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

#include <librevenge/librevenge.h>

#include "MWAWCell.hxx"
#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWHeader.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPosition.hxx"
#include "MWAWPictMac.hxx"
#include "MWAWPrinter.hxx"
#include "MWAWSpreadsheetListener.hxx"
#include "MWAWStringStream.hxx"

#include "WingzGraph.hxx"

#include "WingzParser.hxx"

/** Internal: the structures of a WingzParser */
namespace WingzParserInternal
{

////////////////////////////////////////
//! Internal: the cell style of a WingzParser
struct Style {
  //! constructor
  Style()
    : m_font()
    , m_backgroundColor(MWAWColor::white())
    , m_lineColor(MWAWColor::black())
    , m_format()
  {
  }
  //! the font
  MWAWFont m_font;
  //! the cell background color
  MWAWColor m_backgroundColor;
  //! the line color (if needed)
  MWAWColor m_lineColor;
  //! the cell custom format (if known)
  std::string m_format;
};

//! Internal: the cell of a WingzParser
struct Cell final : public MWAWCell {
  //! constructor
  explicit Cell(MWAWVec2i pos=MWAWVec2i(0,0))
    : MWAWCell()
    , m_content()
    , m_formula(-1)
  {
    setPosition(pos);
  }
  Cell(Cell const &)=default;
  //! destructor
  ~Cell() final;
  //! the cell content
  MWAWCellContent m_content;
  //! the formula id
  int m_formula;
};

Cell::~Cell()
{
}

////////////////////////////////////////
//! Internal: the spreadsheet data of a WingzParser
struct Spreadsheet {
  //! constructor
  Spreadsheet()
    : m_widthDefault(74)
    , m_widthCols()
    , m_heightDefault(12)
    , m_heightRows()
    , m_cells()
    , m_cellIdPosMap()
    , m_formulaMap()
    , m_styleMap()
    , m_name("Sheet0")
  {
  }
  //! returns the row size in point
  float getRowHeight(int row) const
  {
    if (row>=0&&row<static_cast<int>(m_heightRows.size()))
      return m_heightRows[size_t(row)];
    return m_heightDefault;
  }
  //! returns the height of a row in point and updated repeated row
  float getRowHeight(int row, int &numRepeated) const
  {
    float res=getRowHeight(row);
    numRepeated=1;
    if (row<0 || row>=static_cast<int>(m_heightRows.size()))
      numRepeated=1000;
    else {
      for (auto r=size_t(row+1); r<m_heightRows.size(); ++r) {
        float nextH=getRowHeight(row+1);
        if (nextH<res || nextH>res)
          break;
        ++numRepeated;
      }
    }
    return res;
  }
  //! convert the m_widthCols in a vector of of point size
  std::vector<float> convertInPoint(std::vector<float> const &list) const
  {
    auto numCols=size_t(getRightBottomPosition()[0]+1);
    std::vector<float> res;
    res.resize(numCols);
    for (size_t i = 0; i < numCols; i++) {
      if (i>=list.size() || list[i] < 0) res[i] = m_widthDefault;
      else res[i] = float(list[i]);
    }
    return res;
  }
  //! returns the page position corresponding to a cell and its relative position(in percent)
  MWAWVec2f getPosition(MWAWVec2i const &cell, MWAWVec2f const &relPos) const
  {
    if (cell[0]<0 || cell[1]<0 || cell[0]>256 || cell[1]>10000) {
      MWAW_DEBUG_MSG(("WingzParserInternal::Spreadsheet::getPosition: the cell's position (%d,%d) seem bads\n", cell[0], cell[1]));
      return MWAWVec2f(0,0);
    }
    float cPos=0;
    for (size_t i = 0; i<=size_t(cell[0]); ++i) {
      float w=(i<m_widthCols.size() && m_widthCols[i]>=0) ? m_widthCols[i] : m_widthDefault;
      if (i<size_t(cell[0]))
        cPos += w;
      else
        cPos+=w*relPos[0];
    }
    float rPos=0;
    for (size_t i = 0; i<=size_t(cell[1]); ++i) {
      float w=(i<m_heightRows.size() && m_heightRows[i]>=0) ? m_heightRows[i] : m_heightDefault;
      if (i<size_t(cell[1]))
        rPos += w;
      else
        rPos+=w*relPos[1];
    }
    return MWAWVec2f(cPos,rPos);
  }
  //! update the cell, ie. look if there is a avalaible formula, ...
  void update(Cell &cell) const;
  /** the default column width */
  float m_widthDefault;
  /** the column size in points */
  std::vector<float> m_widthCols;
  /** the default row height */
  float m_heightDefault;
  /** the row height in points */
  std::vector<float> m_heightRows;
  /** the list of not empty cells */
  std::vector<Cell> m_cells;
  //! the map cellId to cellPos
  std::map<int, MWAWCellContent::FormulaInstruction> m_cellIdPosMap;
  //! the list of formula
  std::map<int, std::vector<MWAWCellContent::FormulaInstruction> > m_formulaMap;
  //! the list of style
  std::map<int, Style> m_styleMap;
  /** the spreadsheet name */
  std::string m_name;
protected:
  /** returns the last Right Bottom cell position */
  MWAWVec2i getRightBottomPosition() const
  {
    int maxX = 0, maxY = 0;
    for (auto const &cell : m_cells) {
      MWAWVec2i const &p = cell.position();
      if (p[0] > maxX) maxX = p[0];
      if (p[1] > maxY) maxY = p[1];
    }
    return MWAWVec2i(maxX, maxY);
  }
};

void Spreadsheet::update(Cell &cell) const
{
  // checkme: is cell.m_formula==0 really a cell with a formula ?
  if (cell.m_formula < 0 || m_formulaMap.find(cell.m_formula)==m_formulaMap.end())
    return;
  // first, we need to update the relative position
  auto formula=m_formulaMap.find(cell.m_formula)->second;
  MWAWVec2i cPos=cell.position();
  for (auto &instr : formula) {
    int numToCheck=0;
    if (instr.m_type==MWAWCellContent::FormulaInstruction::F_Cell)
      numToCheck=1;
    else if (instr.m_type==MWAWCellContent::FormulaInstruction::F_CellList)
      numToCheck=2;
    for (int j=0; j<numToCheck; ++j) {
      for (int c=0; c<2; ++c) {
        if (instr.m_positionRelative[j][c])
          instr.m_position[j][c]+=cPos[c];
        if (instr.m_position[j][c]<0) {
          if (cell.m_formula!=0) {
            MWAW_DEBUG_MSG(("WingzParserInternal::Spreadsheet::update: find some bad cell position\n"));
          }
          return;
        }
      }
    }
  }
  cell.m_content.m_contentType=MWAWCellContent::C_FORMULA;
  cell.m_content.m_formula=formula;
}

////////////////////////////////////////
//! Internal: the state of a WingzParser
struct State {
  //! constructor
  State()
    : m_encrypted(false)
    , m_spreadsheet()
    , m_numPages(0)
    , m_headerHeight(0)
    , m_footerHeight(0)
  {
  }

  //! returns the pattern percent corresponding to an id and a version
  static bool getPatternPercent(int patId, int vers, float &percent);

  //! a flag to know if the data is encrypted
  bool m_encrypted;
  //! the spreadsheet
  Spreadsheet m_spreadsheet;
  int m_numPages /** the number of page of the final document */;

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

bool State::getPatternPercent(int patId, int vers, float &perc)
{
  if (vers==2) {
    if (patId<0 || patId>38) {
      MWAW_DEBUG_MSG(("WingzParserInternal::State::getPatternPercent: can not find patId=%d\n", patId));
      return false;
    }
    static float const percent[]= { 0/*none*/, 1, 0.9f, 0.7f, 0.5f, 0.7f, 0.5f, 0.7f, 0.2f, 0.3f, 0.1f, 0.3f, 0.3f,
                                    0.04f, 0.1f, 0.2f, 0.5f, 0.2f, 0.2f, 0.4f, 0, 0.1f, 0.2f, 0.3f, 0.3f, 0.5f,
                                    0.3f, 0.3f, 0.2f, 0.2f, 0.2f, 0.3f, 0.3f, 0.2f, 0.3f, 0.4f, 0.4f, 0.5f, 0.4f
                                  };
    perc=percent[patId];
    return true;
  }

  if (patId<0 || patId>=64) {
    MWAW_DEBUG_MSG(("WingzParserInternal::State::getPatternPercent: can not find patId=%d\n", patId));
    return false;
  }
  static float const percent[]= {
    0.0f, 1.0f, 0.968750f, 0.93750f, 0.875f, 0.750f, 0.5f, 0.250f,
    0.250f, 0.18750f, 0.1875f, 0.1250f, 0.0625f, 0.0625f, 0.031250f, 0.0f,
    0.75f, 0.5f, 0.25f, 0.3750f, 0.25f, 0.1250f, 0.25f, 0.1250f,
    0.75f, 0.5f, 0.25f, 0.3750f, 0.25f, 0.1250f, 0.25f, 0.1250f,
    0.75f, 0.5f, 0.5f, 0.5f, 0.5f, 0.25f, 0.25f, 0.234375f,
    0.6250f, 0.3750f, 0.1250f, 0.25f, 0.218750f, 0.218750f, 0.1250f, 0.093750f,
    0.5f, 0.5625f, 0.4375f, 0.3750f, 0.218750f, 0.281250f, 0.1875f, 0.093750f,
    0.593750f, 0.5625f, 0.515625f, 0.343750f, 0.3125f, 0.25f, 0.25f, 0.234375f
  };
  perc=percent[patId];
  return true;
}
}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
WingzParser::WingzParser(MWAWInputStreamPtr const &input, MWAWRSRCParserPtr const &rsrcParser, MWAWHeader *header)
  : MWAWSpreadsheetParser(input, rsrcParser, header)
  , m_state()
  , m_graphParser(new WingzGraph(*this))
{
  init();
}

WingzParser::~WingzParser()
{
}

void WingzParser::init()
{
  resetSpreadsheetListener();
  setAsciiName("main-1");

  m_state.reset(new WingzParserInternal::State);

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

MWAWVec2f WingzParser::getPosition(MWAWVec2i const &cell, MWAWVec2f const &relPos) const
{
  return m_state->m_spreadsheet.getPosition(cell, relPos);
}

////////////////////////////////////////////////////////////
// the parser
////////////////////////////////////////////////////////////
void WingzParser::parse(librevenge::RVNGSpreadsheetInterface *docInterface)
{
  if (!getInput().get() || !checkHeader(nullptr))  throw(libmwaw::ParseException());
  bool ok = true;
  try {
    if (m_state->m_encrypted)
      ok = decodeEncrypted();
    if (ok) {
      // create the asciiFile
      ascii().setStream(getInput());
      ascii().open(asciiName());
      checkHeader(nullptr);
      ok = createZones();
    }
    if (ok) {
      createDocument(docInterface);
      sendSpreadsheet();
    }
  }
  catch (...) {
    MWAW_DEBUG_MSG(("WingzParser::parse: exception catched when parsing\n"));
    ok = false;
  }

  ascii().reset();
  resetSpreadsheetListener();
  if (!ok) throw(libmwaw::ParseException());
}

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

  // create the page list
  m_state->m_numPages = 1;
  MWAWPageSpan ps(getPageSpan());
  ps.setPageSpan(1);
  std::vector<MWAWPageSpan> pageList(1,ps);
  //
  MWAWSpreadsheetListenerPtr listen(new MWAWSpreadsheetListener(*getParserState(), pageList, documentInterface));
  setSpreadsheetListener(listen);
  listen->startDocument();
}


////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool WingzParser::createZones()
{
  MWAWInputStreamPtr input = getInput();
  input->setReadInverted(true);
  input->seek(13, librevenge::RVNG_SEEK_SET);
  if (!readPreferences()) {
    ascii().addPos(input->tell());
    ascii().addNote("Entries(Loose)");
    if (!findNextZone())
      return false;
  }
  if (!readSpreadsheet()) return false;
  if (!input->isEnd()) {
    ascii().addPos(input->tell());
    ascii().addNote("Entries(Loose)");
  }
  return true;
}

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

////////////////////////////////////////////////////////////
// read the preferences zone
////////////////////////////////////////////////////////////

bool WingzParser::readPreferences()
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  int const vers=version();
  if (!input->checkPosition(pos+172+2*vers)) {
    MWAW_DEBUG_MSG(("WingzParser::readPreferences: the zone seems to short\n"));
    return false;
  }

  libmwaw::DebugStream f;
  f << "Entries(Preferences):";
  auto type=static_cast<int>(input->readULong(1));
  auto val=static_cast<int>(input->readULong(1));
  auto dSz=static_cast<int>(input->readULong(2));
  auto id=static_cast<int>(input->readULong(2));
  long endPos=pos+4+dSz;
  if (type!=0 || !input->checkPosition(endPos)) return false;
  if (val!=0x80) f << "f0=" << val << ",";
  if (id) f << "id=" << id << ",";
  ascii().addDelimiter(input->tell(), '|');
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+18+2*vers, librevenge::RVNG_SEEK_SET);

  for (int i=0; i < 4; ++i) {
    pos=input->tell();
    f.str("");
    f << "Preferences-" << i << ":";
    int const sz[] = { 42, 42, 30, 51};
    input->seek(pos+sz[i], librevenge::RVNG_SEEK_SET);
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }

  // now a font (or maybe a font list)
  pos=input->tell();
  f.str("");
  f << "Preferences-Fonts:";
  val=static_cast<int>(input->readULong(1));
  if (val) f << "f0=" << val << ",";
  auto sz=long(input->readULong(1));
  long fontEndPos=pos+sz;
  if (!input->checkPosition(pos+sz)) {
    MWAW_DEBUG_MSG(("WingzParser::readPreferences: the fonts zone seems to short\n"));
    return false;
  }
  auto N=static_cast<int>(input->readULong(1));
  f << "N=" << N << ",";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  for (int i=0; i < N; ++i) {
    f.str("");
    f << "Preferences-Font" << i << ":";
    pos=input->tell();
    auto fSz=static_cast<int>(input->readULong(1));
    if (pos+1+fSz>fontEndPos) {
      MWAW_DEBUG_MSG(("WingzParser::readPreferences: the %d font size seems bad\n", i));
      f << "###";
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      input->seek(fontEndPos, librevenge::RVNG_SEEK_SET);
      return true;
    }
    std::string name("");
    for (int c=0; c<fSz; ++c) name+=char(input->readULong(1));
    f << name;
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  if (input->tell()!=fontEndPos) {
    ascii().addPos(input->tell());
    ascii().addNote("Preferences-Fontsend");
    MWAW_DEBUG_MSG(("WingzParser::readPreferences: find extra data\n"));
    input->seek(fontEndPos, librevenge::RVNG_SEEK_SET);
  }

  // last unknown
  pos=input->tell();
  if (!input->checkPosition(pos+237)) {
    MWAW_DEBUG_MSG(("WingzParser::readPreferences: the last zone seems to short\n"));
    return false;
  }

  f.str("");
  f << "Preferences-B0:";
  val=static_cast<int>(input->readLong(2));
  if (val) f << "f0=" << val << ",";
  val=static_cast<int>(input->readLong(2));
  if (val) f << "graph[num]=" << val << ",";
  for (int i=0; i < 10; ++i) { // f7,f8 related to page size?
    val=static_cast<int>(input->readLong(2));
    if (val)
      f << "f" << i+1 << "=" << val << ",";
  }
  auto sSz=static_cast<int>(input->readULong(1));
  if (!input->checkPosition(pos+25+sSz)) {
    MWAW_DEBUG_MSG(("WingzParser::readPreferences: auto save name seems bad\n"));
    f << "####";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    return false;
  }
  std::string name("");
  for (int i=0; i < sSz; ++i)
    name += char(input->readULong(1));
  f << name << ",";
  for (int i=0; i < 8; ++i) {
    val=static_cast<int>(input->readLong(2));
    if (val)
      f << "g" << i << "=" << val << ",";
  }
  int dim[4];
  for (auto &d : dim) d=static_cast<int>(input->readLong(2));
  f << "select?=" << dim[0] << "x" << dim[1] << "<->" << dim[2] << "x" << dim[3] << ",";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  pos=input->tell();
  ascii().addPos(pos);
  ascii().addNote("Preferences-B1");
  input->seek(pos+96, librevenge::RVNG_SEEK_SET);

  pos=input->tell();
  ascii().addPos(pos);
  ascii().addNote("Preferences-B2");
  input->seek(pos+58, librevenge::RVNG_SEEK_SET);

  pos=input->tell();
  f.str("");
  f << "Preferences[passwd]";
  for (int i=0; i<2; ++i) {
    input->seek(pos+i*17, librevenge::RVNG_SEEK_SET);
    auto len=static_cast<int>(input->readULong(1));
    if (!len) continue;
    if (len>16) {
      MWAW_DEBUG_MSG(("WingzParser::readPreferences: passwd size seems bad\n"));
      f << "###len" << i << "=" << len << ",";
      break;
    }
    std::string passwd("");
    for (int c=0; c<len; ++c) passwd += char(input->readULong(1));
    f << passwd << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  if (vers==1)
    input->seek(endPos, librevenge::RVNG_SEEK_SET);
  else
    input->seek(pos+34, librevenge::RVNG_SEEK_SET);

  return true;
}


////////////////////////////////////////////////////////////
// spreadsheet
////////////////////////////////////////////////////////////
bool WingzParser::readSpreadsheet()
{
  MWAWInputStreamPtr input = getInput();
  libmwaw::DebugStream f;
  int const vers=version();
  int const headerSize=vers==1?4:6;
  while (!input->isEnd()) {
    long pos=input->tell();
    auto type=static_cast<int>(input->readULong(1));
    auto val=static_cast<int>(input->readULong(1));
    auto dSz=static_cast<int>(input->readULong(2));
    if (type!=0xFF && input->isEnd()) {
      MWAW_DEBUG_MSG(("WingzParser::readSpreadsheet: can not read some zone\n"));
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    std::string name("");
    if (type<=0x10) {
      static char const *wh[]= {
        "", "SheetSize", "SheetSize", "", "", "", "", "CellName",
        "Formula", "Style", "SheetErr", "Sheet2Err", "", "SheetMcro", "Graphic", "",
        "PrintInfo"
      };
      name=wh[type];
    }
    if (name.empty()) {
      std::stringstream s;
      s << "ZSheet" << type;
      name = s.str();
    }
    f.str("");
    f << "Entries(" << name << "):";
    if (val!=0x80) f << "fl=" << std::hex << val << std::dec << ",";

    bool ok=true;
    switch (type) {
    case 1: // col size
    case 2: // row size
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=readSpreadsheetSize();
      break;
    case 18:
    case 19:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=readSpreadsheetPBreak();
      break;
    case 3: // a list of int?
      ok=input->checkPosition(pos+headerSize+dSz);
      if (!ok) break;
      if (vers>1) {
        val=static_cast<int>(input->readLong(2));
        if (val) f << "id=" << val << ",";
      }
      if ((dSz%2)) {
        MWAW_DEBUG_MSG(("WingzParser::readSpreadsheet: find some data in zone3\n"));
        f << "###";
        ascii().addDelimiter(pos+headerSize,'|');
      }
      else if (dSz) {
        f << "val=[";
        for (int i=0; i<dSz/2; ++i) f << input->readLong(2) << ",";
        f << "],";
      }
      input->seek(pos+headerSize+dSz, librevenge::RVNG_SEEK_SET);
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      break;
    case 4: // never find this block with data ...
      ok=input->checkPosition(pos+headerSize+dSz);
      if (!ok) break;
      if (vers>1) {
        val=static_cast<int>(input->readLong(2));
        if (val) f << "id=" << val << ",";
      }
      if (dSz) {
        MWAW_DEBUG_MSG(("WingzParser::readSpreadsheet: find some data in zone4\n"));
        f << "###";
        ascii().addDelimiter(pos+headerSize,'|');
      }
      input->seek(pos+headerSize+dSz, librevenge::RVNG_SEEK_SET);
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      break;
    case 5:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=readSpreadsheetZone5();
      break;
    case 6: { // only in v1?
      ok=input->checkPosition(pos+headerSize+(vers==1 ? 2 : 0) +dSz);
      if (!ok) break;
      val=static_cast<int>(input->readLong(2));
      if (val) f << "id=" << val << ",";
      auto fSz=static_cast<int>(input->readULong(1));
      if (dSz<1 || dSz!=1+fSz) {
        MWAW_DEBUG_MSG(("WingzParser::readSpreadsheet: zone 6 size seems bad\n"));
        f << "###";
      }
      else {
        std::string text("");
        for (int i=0; i<fSz; ++i) text += char(input->readULong(1));
        f << text << ",";
      }
      input->seek(pos+headerSize+(vers==1 ? 2 : 0)+dSz, librevenge::RVNG_SEEK_SET);
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      break;
    }
    case 7:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=readSpreadsheetCellName();
      break;
    case 8:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=readFormula();
      break;
    case 9:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=readSpreadsheetStyle();
      break;
    case 0xa: { // seems related to error in formula 0x1a
      ok=input->checkPosition(pos+6+dSz);
      if (!ok) break;
      val=static_cast<int>(input->readLong(2));
      if (val) f << "id=" << val << ",";
      auto fSz=static_cast<int>(input->readULong(1));
      if (dSz<1 || dSz!=1+fSz) {
        MWAW_DEBUG_MSG(("WingzParser::readSpreadsheet: SheetErr size seems bad\n"));
        f << "###";
      }
      else {
        std::string text("");
        for (int i=0; i<fSz; ++i) text += char(input->readULong(1));
        f << text << ",";
      }
      input->seek(pos+6+dSz, librevenge::RVNG_SEEK_SET);
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      break;
    }
    case 0xb: { // seems related to error in formula 0x1a
      ok=input->checkPosition(pos+6+dSz);
      if (!ok) break;
      val=static_cast<int>(input->readLong(2));
      if (val) f << "id=" << val << ",";
      if ((vers==1&&dSz<2) || (vers==2&&dSz<4)) {
        MWAW_DEBUG_MSG(("WingzParser::readSpreadsheet: Sheet2Err size seems bad\n"));
        f << "###";
      }
      else {
        f << "pos=" << input->readULong(1) << "x" << input->readULong(1) << ",";
        val = static_cast<int>(input->readLong(1));
        if (val) f << "#g0=" << val << ",";
        std::string text("");
        for (int i=0; i<dSz-3; ++i) text += char(input->readULong(1));
        f << text << ",";
      }
      input->seek(pos+6+dSz, librevenge::RVNG_SEEK_SET);
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      break;
    }
    case 0xc:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=readSpreadsheetCellList();
      break;
    case 0xd:
      val=static_cast<int>(input->readLong(2));
      if (val) f << "id=" << val << ",";
      ok=readMacro();
      if (!ok) break;
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      break;
    case 0xe:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=m_graphParser->readGraphic();
      if (!ok) {
        ascii().addPos(pos);
        ascii().addNote("Entries(Graphic):###");
        ok=findNextZone(0xe) && input->tell()>pos+46;
      }
      break;
    case 0xf:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=m_graphParser->readEndGroup();
      break;
    case 0x10:
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      ok=readPrintInfo();
      break;
    case 0xff: // end
      if (val==0xf && dSz==0) {
        ascii().addPos(pos);
        ascii().addNote("_");
        return true;
      }
      ok=false;
      break;
    default:
      ok=false;
    }
    if (ok) continue;
    input->seek(pos+4, librevenge::RVNG_SEEK_SET);
    if (vers==1) {
      if (type==6 || type==7 || type==0x11) dSz+=2;
    }
    else {
      if (type==0xc) dSz+=4;
      else if (type==0xe) dSz+=2;
      else if (type==0x10) dSz+=14;
    }
    if (!type || (vers==2 && !val) || (type>24 && !(vers==1&&type>100 && type<104)) ||
        (val&0x3F) || !input->checkPosition(pos+headerSize+dSz)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    auto id=static_cast<int>(input->readLong(2));
    if (id) f << "id=" << id << ",";
    if (input->tell() != pos+headerSize+dSz)
      ascii().addDelimiter(input->tell(), '|');
    input->seek(pos+headerSize+dSz, librevenge::RVNG_SEEK_SET);
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  return true;
}

bool WingzParser::readSpreadsheetCellList()
{
  MWAWInputStreamPtr input = getInput();
  int const vers=version();
  long pos=input->tell();
  auto type=static_cast<int>(input->readULong(1));
  if (type!=12) return false;
  auto val=static_cast<int>(input->readULong(1));
  auto dSz= static_cast<int>(input->readULong(2));
  auto row=static_cast<int>(input->readLong(2));
  auto firstCol=static_cast<int>(input->readLong(2));
  long endPos=pos+(vers==1 ? 6 : 10)+dSz;
  libmwaw::DebugStream f;
  f << "Entries(SheetCell)[row=" << row << "]:";
  if (firstCol) f << "first[col]=" << firstCol << ",";
  if (val!=0x40) f << "fl=" << std::hex << val << std::dec << ",";
  if (!input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetCellList: find bad size for data\n"));
    return false;
  }
  val=static_cast<int>(input->readLong(2));
  if (val) f << "f0=" << val << ",";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  while (!input->isEnd()) {
    MWAWVec2i cellPos(firstCol++, row);
    pos=input->tell();
    if (pos>=endPos) break;
    type=static_cast<int>(input->readULong(1));
    f.str("");
    f << "SheetCell[" << cellPos << "]:type=" << (type&0xf);
    if (type&0xf0) {
      f << "[high=" << (type>>4) << "]";
      type &=0xf;
    }
    f << ",";
    if (type==0) { // empty cell
      ascii().addPos(pos);
      ascii().addNote(f.str().c_str());
      continue;
    }
    if (pos+(vers==1 ? 4 : 6)>endPos) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    WingzParserInternal::Cell cell(cellPos);
    val=static_cast<int>(input->readULong(1));
    if (val&0xF) {
      int borders=0;
      f << "bord=";
      if (val&1) {
        borders|=libmwaw::LeftBit;
        f << "L";
      }
      if (val&2) {
        borders|=libmwaw::RightBit;
        f << "R";
      }
      if (val&4) {
        borders|=libmwaw::TopBit;
        f << "T";
      }
      if (val&8) {
        borders|=libmwaw::BottomBit;
        f << "B";
      }
      f << ",";
      cell.setBorders(borders, MWAWBorder());
    }
    if (val&0xF0) f << "f0=" << (val>>4) << ",";

    MWAWCell::Format format;
    val=static_cast<int>(input->readULong(1));
    format.m_digits= val&0xf;
    if (format.m_digits!=2) f << "digits=" << format.m_digits << ",";
    switch (val>>4) {
    case 0: // general
      break;
    case 1:
      format.m_format=MWAWCell::F_NUMBER;
      format.m_numberFormat=MWAWCell::F_NUMBER_DECIMAL;
      break;
    case 2:
      format.m_format=MWAWCell::F_NUMBER;
      format.m_numberFormat=MWAWCell::F_NUMBER_CURRENCY;
      break;
    case 3:
      format.m_format=MWAWCell::F_NUMBER;
      format.m_numberFormat=MWAWCell::F_NUMBER_PERCENT;
      break;
    case 4:
      format.m_format=MWAWCell::F_NUMBER;
      format.m_numberFormat=MWAWCell::F_NUMBER_SCIENTIFIC;
      break;
    case 5:
      format.m_format=MWAWCell::F_DATE;
      format.m_DTFormat="%b %d %y:";
      break;
    case 6:
      format.m_format=MWAWCell::F_DATE;
      format.m_DTFormat="%b %d";
      break;
    case 7:
      format.m_format=MWAWCell::F_DATE;
      format.m_DTFormat="%b %y";
      break;
    case 8:
      format.m_format=MWAWCell::F_DATE;
      format.m_DTFormat="%m/%d/%y";
      break;
    case 9:
      format.m_format=MWAWCell::F_DATE;
      format.m_DTFormat="%m/%d";
      break;
    case 10:
      format.m_format=MWAWCell::F_TIME;
      format.m_DTFormat="%I:%M:%S %p";
      break;
    case 11:
      format.m_format=MWAWCell::F_TIME;
      format.m_DTFormat="%I:%M %p";
      break;
    case 12:
      format.m_format=MWAWCell::F_TIME;
      format.m_DTFormat="%H:%M:%S";
      break;
    case 13:
      format.m_format=MWAWCell::F_TIME;
      format.m_DTFormat="%H:%M";
      break;
    case 14:
      MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetCellList: find cell with custom format\n"));
      f << "format=custom,";
      break;
    default:
      MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetCellList: find cell with format=15\n"));
      f << "##format=15,";
      break;
    }
    val=static_cast<int>(input->readULong(1));
    bool ok=true;
    switch ((val>>4)&7) {
    case 0: // general
      break;
    case 1:
      cell.setHAlignment(MWAWCell::HALIGN_LEFT);
      f << "align=left,";
      break;
    case 2:
      cell.setHAlignment(MWAWCell::HALIGN_CENTER);
      f << "align=center,";
      break;
    case 3:
      cell.setHAlignment(MWAWCell::HALIGN_RIGHT);
      f << "align=right,";
      break;
    default:
      break;
    }
    if (val&0x8F) f << "f1=" << std::hex << (val&0x8F) << std::dec << ",";
    val=static_cast<int>(input->readLong(2));
    f << "style=" << val << ",";
    if (m_state->m_spreadsheet.m_styleMap.find(val)==m_state->m_spreadsheet.m_styleMap.end()) {
      f << "#style,";
      MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetStyle: can not find a style\n"));
    }
    else {
      auto const &style=m_state->m_spreadsheet.m_styleMap.find(val)->second;
      cell.setFont(style.m_font);
      if (!style.m_backgroundColor.isWhite())
        cell.setBackgroundColor(style.m_backgroundColor);
    }
    WingzParserInternal::Style style;
    f << "format=[" << format << "],";
    if (type!=1) {
      cell.m_formula=static_cast<int>(input->readLong(2));
      // checkme: does cell.m_formula==0 means also no formula ?
      if (cell.m_formula!=-1)
        f << "formula=" << cell.m_formula << ",";
    }
    MWAWCellContent &content=cell.m_content;
    long dPos=input->tell();
    switch (type) {
    case 1: // only style
      break;
    case 2:
    case 3: {
      ok=false;
      if (dPos+1>endPos)
        break;
      auto fSz=static_cast<int>(input->readULong(1));
      if (dPos+1+fSz>endPos)
        break;
      if (format.m_format==MWAWCell::F_UNKNOWN)
        format.m_format=MWAWCell::F_TEXT;
      if (content.m_contentType!=MWAWCellContent::C_FORMULA)
        content.m_contentType=MWAWCellContent::C_TEXT;
      content.m_textEntry.setBegin(input->tell());
      content.m_textEntry.setLength(fSz);
      std::string text("");
      for (int i=0; i<fSz; ++i) text += char(input->readULong(1));
      f << text;
      input->seek(dPos+1+fSz, librevenge::RVNG_SEEK_SET);
      ok=true;
      break;
    }
    case 4:
      if (dPos+2>endPos) {
        ok=false;
        break;
      }
      if (format.m_format==MWAWCell::F_UNKNOWN)
        format.m_format=MWAWCell::F_NUMBER;
      if (content.m_contentType!=MWAWCellContent::C_FORMULA)
        content.m_contentType=MWAWCellContent::C_NUMBER;
      content.setValue(std::numeric_limits<double>::quiet_NaN());
      f << "nan" << input->readLong(2) << ",";
      break;
    case 5: { // style + double
      if (dPos+8>endPos) {
        ok=false;
        break;
      }
      double value;
      bool isNAN;
      if (format.m_format==MWAWCell::F_UNKNOWN)
        format.m_format=MWAWCell::F_NUMBER;
      if (content.m_contentType!=MWAWCellContent::C_FORMULA)
        content.m_contentType=MWAWCellContent::C_NUMBER;
      if (input->readDoubleReverted8(value, isNAN))
        content.setValue(value);
      f << value;
      input->seek(dPos+8, librevenge::RVNG_SEEK_SET);
      break;
    }
    default:
      ok=false;
      break;
    }
    cell.setFormat(format);
    // change the reference date from 1/1/1904 to 1/1/1900
    if (format.m_format==MWAWCell::F_DATE && content.isValueSet())
      content.setValue(content.m_value+1460.);

    m_state->m_spreadsheet.m_cells.push_back(cell);
    if (!ok) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  pos=input->tell();
  if (pos==endPos) return true;
  MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetCellList: find some extra data\n"));
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  ascii().addPos(pos);
  ascii().addNote("SheetCell-end:");
  return true;
}

// style
bool WingzParser::readSpreadsheetCellName()
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  auto type=static_cast<int>(input->readULong(1));
  if (type!=7) return false;
  auto val=static_cast<int>(input->readULong(1));
  auto dSz= static_cast<int>(input->readULong(2));
  auto id=static_cast<int>(input->readLong(2));
  long endPos=pos+6+dSz;
  libmwaw::DebugStream f;
  f << "Entries(CellName)[" << id << "]:";
  if (val!=0x40) f << "fl=" << std::hex << val << std::dec << ",";
  if (dSz<10 || !input->checkPosition(pos+6+dSz)) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetCellName: find bad size for data\n"));
    return false;
  }
  val=static_cast<int>(input->readLong(2));
  if (val!=-1) f << "f0=" << val << ",";
  auto sSz=static_cast<int>(input->readULong(1));
  if ((sSz!=7&&sSz!=12) || input->tell()+sSz>endPos) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetCellName: can not determine the block type\n"));
    f << "###";
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
    input->seek(endPos, librevenge::RVNG_SEEK_SET);
    return true;
  }
  MWAWCellContent::FormulaInstruction instr;
  if (sSz==7) { // a cell
    val=static_cast<int>(input->readLong(1));
    if (val!=7) f << "f1=" << val << ",";
    int cell[2];
    for (auto &c : cell) c=static_cast<int>(input->readLong(2));
    instr.m_type=MWAWCellContent::FormulaInstruction::F_Cell;
    instr.m_position[0]=MWAWVec2i(cell[1],cell[0]);
    instr.m_positionRelative[0]=MWAWVec2b(false,false);
    f << "cell=" << instr.m_position[0] << ",";
  }
  else { // a cell list
    for (int i=0; i< 2; ++i) {
      val=static_cast<int>(input->readLong(1));
      if (val!=7) f << "f" << i+1 << "=" << val << ",";
    }
    int cell[4];
    for (auto &c : cell) c=static_cast<int>(input->readLong(2));
    instr.m_type=MWAWCellContent::FormulaInstruction::F_CellList;
    instr.m_position[0]=MWAWVec2i(cell[2], cell[0]);
    instr.m_position[1]=MWAWVec2i(cell[3], cell[1]);
    instr.m_positionRelative[0]=instr.m_positionRelative[1]=MWAWVec2b(false,false);
    f << "cell=" << instr.m_position[0] << "<->" << instr.m_position[1] << ",";
  }
  val=static_cast<int>(input->readLong(1));
  if (val!=-1) f << "g0=" << val << ",";

  sSz=static_cast<int>(input->readULong(1));
  if (input->tell()+sSz>endPos) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetCellName: style name seems bad\n"));
    f << "###";
  }
  else {
    std::string name("");
    for (int i=0; i<sSz; ++i) name+=char(input->readULong(1));
    f << name << ",";
    if (input->tell()!=endPos) {
      MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetCellName: find some extra data\n"));
      ascii().addDelimiter(input->tell(), '|');
    }
  }
  m_state->m_spreadsheet.m_cellIdPosMap[id]=instr;
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

bool WingzParser::readSpreadsheetStyle()
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  auto type=static_cast<int>(input->readULong(1));
  if (type!=9) return false;
  auto val=static_cast<int>(input->readULong(1));
  auto dSz= static_cast<int>(input->readULong(2));
  auto id=static_cast<int>(input->readLong(2));
  long endPos=pos+6+dSz;

  libmwaw::DebugStream f;
  f << "Entries(Style)[" << id << "]:";
  if (val!=0x40) f << "fl=" << std::hex << val << std::dec << ",";
  if (dSz<26 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetStyle: find bad size for data\n"));
    return false;
  }
  WingzParserInternal::Style style;
  val=static_cast<int>(input->readLong(2));
  if (val!=1) f << "used=" << val << ",";
  val=static_cast<int>(input->readLong(2)); // always 0?
  if (val) f << "f0=" << val << ",";
  MWAWFont &font=style.m_font;
  font.setSize(float(input->readULong(2)));
  auto flag=static_cast<int>(input->readULong(2));
  uint32_t flags=0;
  if (flag&0x1) flags |= MWAWFont::boldBit;
  if (flag&0x2) flags |= MWAWFont::italicBit;
  if (flag&0x4) font.setUnderlineStyle(MWAWFont::Line::Simple);
  if (flag&0x8) flags |= MWAWFont::embossBit;
  if (flag&0x10) flags |= MWAWFont::shadowBit;
  if (flag&0xFF60)
    f << "#font[flag]=" << std::hex << (flag&0xFF60) << std::dec << ",";
  font.setFlags(flags);

  val=static_cast<int>(input->readLong(1));
  if (val==1)
    f << "hasCustomFmt,";
  else if (val) f << "#hasCustomFmt=" << val << ",";
  bool hasCustomFormat=val==1;

  int patId=0;
  MWAWColor colors[4];
  for (int i=0; i<4; ++i) { // front, back, unknown,font color
    val=static_cast<int>(input->readULong(4));
    int col=((val>>16)&0xFF)|(val&0xFF00)|((val&0xFF)<<16);
    int high=(val>>24);
    colors[i]=MWAWColor(uint32_t(col)|0xFF000000);
    switch (i) {
    case 0:
      patId=high;
      if (patId) f << "patId=" << patId << ",";
      if (col) f << "backColor=" << std::hex << col << std::dec << ",";
      break;
    case 1:
      if (col!=0xFFFFFF) f << "frontColor=" << std::hex << col << std::dec << ",";
      if (high) f << "g0=" << high << ",";
      break;
    case 2:
      style.m_lineColor=colors[i];
      if (col) f << "lineColor=" << std::hex << col << std::dec << ",";
      if (high!=1) f << "g1=" << high << ",";
      break;
    case 3:
      font.setColor(MWAWColor(uint32_t(col)));
      if (high) f << "g2=" << high << ",";
      break;
    default:
      break;
    }
  }
  float percent;
  if (patId && m_state->getPatternPercent(patId, version(), percent)) {
    style.m_backgroundColor=MWAWColor::barycenter(percent, colors[0], 1.f-percent, colors[1]);
    if (!style.m_backgroundColor.isWhite())
      f << "cellColor=" << style.m_backgroundColor << ",";
  }
  auto nSz=static_cast<int>(input->readULong(1));
  if (26+nSz>dSz) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetStyle: the name size seems bad\n"));
    f << "###";
  }
  else {
    std::string name("");
    for (int i=0; i < nSz; ++i) name+=char(input->readULong(1));
    font.setId(getParserState()->m_fontConverter->getId(name));
  }
  f << font.getDebugString(getParserState()->m_fontConverter) << ",";

  if (hasCustomFormat && input->tell()!=endPos) {
    long actPos=input->tell();
    auto fSz=static_cast<int>(input->readULong(1));
    if (actPos+1+fSz<=endPos) {
      std::stringstream form("");
      for (int i=0; i<fSz; ++i) {
        // list of code defining either a variable, special char or normal char
        auto c=static_cast<int>(input->readULong(1));
        switch (c) {
        case 1:
          form << "\\";
          break;
        case 8:
          form << "[day]";
          break;
        case 0x1a:
          form << "%";
          break;
        case 0x2d:
          form << ":";
          break;
        default:
          if (c>0x30) form << char(c);
          else form << "[0x" << std::hex << c << std::dec << "]";
        }
      }
      f << "form=\"" << form.str() << "\",";
    }
    else {
      MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetStyle: can not read custom format\n"));
      f << "##format,";
      input->seek(actPos, librevenge::RVNG_SEEK_SET);
    }
  }

  if (m_state->m_spreadsheet.m_styleMap.find(id)!=m_state->m_spreadsheet.m_styleMap.end()) {
    f << "#id,";
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetStyle: the style %d already exists\n", id));
  }
  else
    m_state->m_spreadsheet.m_styleMap[id]=style;
  if (input->tell()!=endPos) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetStyle: find some extra data\n"));
    ascii().addDelimiter(input->tell(), '|');
    input->seek(endPos, librevenge::RVNG_SEEK_SET);
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  return true;
}

////////////////////////////////////////////////////////////
// formula
////////////////////////////////////////////////////////////
namespace WingzParserInternal
{
struct Functions {
  char const *m_name;
  int m_arity;
};

static Functions const s_listFunctions[] = {
  { "", -2} /*cell*/,{ "", -2} /*cell*/,{ "", -2} /*cell*/,{ "", -2} /*cell*/,
  { "", -2} /*cell*/,{ "", -2} /*cell*/,{ "", -2} /*cell*/,{ "", -2} /*cell*/,
  { "", -2} /*list cell*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  // 10
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*double*/,{ "", -2} /* text*/,{ "", -2} /*1a entry error */,{ "", -2} /*UNKN*/,
  { "", -2}/*if separator?*/,{ "", -2} /*if separator?*/,{ "", -2} /* convert to?*/,{ "", -2} /*convert to bool*/,
  // 20
  { "", -2} /*UNKN*/, { "", -2},{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "(", 1} /*spec()*/,{ "", -2}/*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*convert to?*/,{ "", -2} /*UNKN*/,{ "", -2} /*+number*/,{ "", -2} /*-number*/,
  { "", -2} /* *number*/,{ "", -2} /*/number*/,{ "+", 2},{ "-", 2},
  // 30
  { "*", 2},{ "/", 2}, { "-", 1},{ "", -2} /*UNKN*/,
  { "^", 2},{ "Concatenate", 2},{ "And", 2},{ "Or", 2},
  { "Not", 1},{ "=", 2},{ "<", 2},{ "<=", 2},
  { ">", 2},{ ">=", 2},{ "<>", 2},{ "", -2} /*UNKN*/,
  // 40
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "False", 0},{ "True", 0}, { "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "E", 0}/*exp 1*/,{ "Pi", 0},{ "IsErr", 1},
  // 50
  { "IsNA", 1},{ "IsNumber", 1},{ "IsString", 1},{ "IsBlank", 1},
  { "", -2} /*UNKN*/,{ "DAverage", 3},{ "DCount", 3},{ "DMax", 3},
  { "DMin", 3},{ "DStDev", 3},{ "DStDevP", 3}/*checkme*/,{ "DSum", 3},
  { "DSumSq", 3},{ "DVar", 3},{ "DVarP", 3}/*checkme*/,{ "Now", 0},
  // 60
  { "CMonth", 1},{ "CWeekday", 1},{ "DateValue", 1},{ "Day", 1},
  { "DayName", 1},{ "Month", 1},{ "MonthName", 1},{ "Year", 1},
  { "ADate", 2},{ "AddDays", 2},{ "EDate", 2}/* add month*/,{ "AddYears", 2},
  { "Date", 3},{ "Hour", 1},{ "Minute", 1},{ "Second", 1},
  // 70
  { "TimeValue", 1},{ "AddHours", 2},{ "AddMinutes",2},{ "AddSeconds", 2},
  { "Atime", 2},{ "Time", 3},{ "CTERM", 3},{ "FV", 3},
  { "FVL", 3},{ "Interest", 3},{ "LoanTerm", 3},{ "PMT", 3},
  { "Principal", 3},{ "PV", 3},{ "PVL", 3},{ "Rate", 3},
  // 80
  { "SLN", 3},{ "", -2} /*UNKN*/,{ "DDB", 4},{ "SYD", 4},
  { "BondPrice", 5},{ "BondYTM", 5},{ "IRR", -1},{ "NPV", -1},
  { "Acosh", 1},{ "Asinh", 1} /*UNKN*/,{ "Atanh", 1},{ "Cosh", 1},
  { "Sinh", 1},{ "Tanh", 1},{ "If", 3},{ "Choose", -1},
  // 90
  { "NA", 0} /*Err*/,{ "NA", 0},{ "Guess", 0},{ "Abs", 1},
  { "Factorial", 1},{ "Int", 1},{ "Sign", 1},{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "Mod", 2},{ "Round", 2},
  { "Goal", 3},{ "Rand", 0},{ "Exponential", 1} /*odd: maybe set*/,{ "Normal", 1},
  // a0
  { "Uniform", 1},{ "Average", -1},{ "Count", -1},{ "Max", -1},
  { "Min", -1},{ "StD", -1},{ "StDev", -1},{ "Sum", -1},
  { "SumSq", -1},{ "Var", -1},{ "VarP", -1}/*checkme*/,{ "Char", 1},
  { "Code", 1},{ "Length", 1},{ "Lower", 1},{ "", -2} /*UNKN*/,
  // b0
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "Currency", 1},{ "Proper", 1},
  { "", -2} /*UNKN*/,{ "Exact", 2},{ "NFormat", 2},{ "Left", 2},
  { "Right", 2},{ "", -2} /*UNKN*/,{ "Collate", 2},{ "Rept", 2},
  { "Find", 3},{ "Match", 3},{ "MID", 3},{ "Replace", 4},
  // c0
  { "Exp", 1},{ "Ln", 1},{ "Log", 1},{ "Logn", 2},
  { "Sqrt", 1},{ "Acos", 1},{ "Asin", 1},{ "Atan", 1},
  { "Cos", 1},{ "Degrees", 1},{ "Radians", 1},{ "Sin", 1},
  { "Tan", 1},{ "Atan2", 2},{ "Col", 0},{ "Row", 0},
  // d0
  { "Cols", 1},{ "", -2} /*UNKN*/,{ "Indirect", 1},{ "Range", 1},
  { "MakeCell", 2},{ "HLookUp", 3},{ "Index", 3},{ "", -2} /*UNKN*/,
  { "MakeRange", 4},{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  // e0
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "FunctE4", 2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "N", 1},{ "Cell", 0},{ "Contains", 2},{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  // f0
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
  { "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,{ "", -2} /*UNKN*/,
};
}

bool WingzParser::readFormula()
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell(), debPos=pos;
  auto type=static_cast<int>(input->readULong(1));
  if (type!=8) return false;
  auto val=static_cast<int>(input->readULong(1));
  auto dSz= static_cast<int>(input->readULong(2));
  long endPos=pos+6+dSz;
  if (dSz<7 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("WingzParser::readFormula: find bad size for data\n"));
    return false;
  }
  auto id=static_cast<int>(input->readLong(2));
  libmwaw::DebugStream f;
  f << "Entries(Formula)[" << id << "]:";
  if (val!=0x40) f << "fl=" << std::hex << val << std::dec << ",";
  for (int i=0; i<2; ++i) { // f0=1|2, f1=0
    val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  val=static_cast<int>(input->readLong(1)); // always 0, maybe related to the equal sign
  if (val) f << "f2=" << val << ",";
  bool ok = true;
  std::vector<std::vector<MWAWCellContent::FormulaInstruction> > stack;
  std::string error("");
  while (long(input->tell()) != endPos) {
    pos = input->tell();
    if (pos > endPos) return false;
    auto wh = static_cast<int>(input->readULong(1));
    if (wh==0xFF) break; // operator=
    double value;
    int arity = 0;
    bool isNan;
    MWAWCellContent::FormulaInstruction instr;
    bool noneInstr=false;
    switch (wh) {
    case 0:
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
    case 6:
    case 7: { // difference with 0-3 ?
      if (pos+1+4>endPos) {
        error="#cell";
        ok=false;
        break;
      }
      int cPos[2];
      for (auto &c : cPos) c=static_cast<int>(input->readLong(2));
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Cell;
      instr.m_position[0]=MWAWVec2i(cPos[1], cPos[0]);
      instr.m_positionRelative[0]=MWAWVec2b((wh&1)==0, (wh&2)==0);
      break;
    }
    case 8: {
      auto typ=static_cast<int>(input->readULong(1));
      if (typ>0xF || pos+1+9>endPos) {
        error="#listCell";
        ok=false;
        break;
      }
      int cPos[4];
      for (auto &c : cPos) c=static_cast<int>(input->readLong(2));
      instr.m_type=MWAWCellContent::FormulaInstruction::F_CellList;
      instr.m_position[0]=MWAWVec2i(cPos[2], cPos[0]);
      instr.m_position[1]=MWAWVec2i(cPos[3], cPos[1]);
      instr.m_positionRelative[0]=MWAWVec2b((typ&2)==0, (typ&1)==0);
      instr.m_positionRelative[1]=MWAWVec2b((typ&8)==0, (typ&4)==0);
      break;
    }
    case 0x9:
    case 0xa: {
      if (pos+1+2>endPos) {
        ok=false;
        break;
      }
      auto cId=static_cast<int>(input->readLong(2));
      if (m_state->m_spreadsheet.m_cellIdPosMap.find(cId)==m_state->m_spreadsheet.m_cellIdPosMap.end()) {
        MWAW_DEBUG_MSG(("WingzParser::readFormula: can not find cell with id\n"));
        std::stringstream s;
        s << "##cellId=" << cId << ",";
        error=s.str();
        ok=false;
        break;
      }
      instr=m_state->m_spreadsheet.m_cellIdPosMap.find(cId)->second;
      break;
    }
    case 0x1a: // error related to Sheet2Err
      if (pos+1+2>endPos) {
        ok=false;
        break;
      }
      f << "f1a=" << input->readLong(1) << "x" << input->readLong(1) <<",";
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
      instr.m_content="NA";
      break;
    case 0x1c:
    case 0x1d: // appear with if, maybe to separate condition to other ?
      if (pos+1+2>endPos) {
        ok=false;
        break;
      }
      noneInstr=true;
      f << "f" << std::hex << wh << std::dec << "=" << input->readLong(2) << ",";
      break;
    case 0x1e: // convert to ?
    case 0x1f: // convert to bool
    case 0x26:
    case 0x27:
    case 0x28: // convert to ?
      noneInstr=true;
      break;
    case 0x18:
    case 0x2a:
    case 0x2b:
    case 0x2c:
    case 0x2d:
      if (endPos-pos<9 || !input->readDoubleReverted8(value, isNan)) {
        error="#number";
        ok = false;
        break;
      }
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Double;
      instr.m_doubleValue=value;
      if (wh>=0x2a&&wh<=0x2d) { // mixed operator + number
        stack.push_back(std::vector<MWAWCellContent::FormulaInstruction>(1,instr));
        static char const *what[]= {"+","-","*","/"};

        instr=MWAWCellContent::FormulaInstruction();
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
        instr.m_content=what[wh-0x2a];
        arity=2;
      }
      break;
    case 0x19: {
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Text;
      auto fSz=static_cast<int>(input->readULong(1));
      if (pos+1+fSz > endPos) {
        ok=false;
        break;
      }
      for (int i=0; i<fSz; ++i) {
        auto c = char(input->readULong(1));
        if (c==0) {
          ok = i+1==fSz;
          break;
        }
        instr.m_content += c;
      }
      break;
    }
    case 0xfe: { // checkme
      if (pos+1+1 > endPos) {
        ok=false;
        break;
      }
      wh=static_cast<int>(input->readULong(1));
      switch (wh) {
      case 0x25:
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
        instr.m_content="CellText";
        arity=1;
        break;
      case 0x27:
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
        instr.m_content="IsRange";
        arity=1;
        break;
      case 0x92:
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
        instr.m_content="ColOf";
        arity=1;
        break;
      case 0x93:
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
        instr.m_content="RowOf";
        arity=1;
        break;
      default: {
        std::stringstream s;
        s << "##FunctExtra" << std::hex << wh << std::dec << ",";
        error=s.str();
        ok = false;
        break;
      }
      }
      break;
    }
    default:
      if (wh < 0xf0 && WingzParserInternal::s_listFunctions[wh].m_arity > -2) {
        instr.m_content=WingzParserInternal::s_listFunctions[wh].m_name;
        instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
        arity=WingzParserInternal::s_listFunctions[wh].m_arity;
      }
      if (instr.m_content.empty()) {
        std::stringstream s;
        s << "##Funct" << std::hex << wh << std::dec << ",";
        error=s.str();
        ok = false;
      }
      if (arity==-1) arity=static_cast<int>(input->readULong(1));
      break;
    }
    if (!ok) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    if (noneInstr) continue;
    std::vector<MWAWCellContent::FormulaInstruction> child;
    if (instr.m_type!=MWAWCellContent::FormulaInstruction::F_Function) {
      child.push_back(instr);
      stack.push_back(child);
      continue;
    }
    size_t numElt = stack.size();
    if (static_cast<int>(numElt) < arity) {
      std::stringstream s;
      s << instr.m_content << "[##" << arity << "]";
      error=s.str();
      ok = false;
      break;
    }
    // special case: AddDays(x,y) => (x+y)
    if (arity==2 && instr.m_content=="AddDays") {
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content="(";
      child.push_back(instr);
      for (int i = 0; i < arity; i++) {
        if (i) {
          instr.m_content="+";
          child.push_back(instr);
        }
        auto const &node=stack[size_t(static_cast<int>(numElt)-arity+i)];
        child.insert(child.end(), node.begin(), node.end());
      }
      instr.m_content=")";
      child.push_back(instr);

      stack.resize(size_t(static_cast<int>(numElt)-arity+1));
      stack[size_t(static_cast<int>(numElt)-arity)] = child;
      continue;
    }
    // special case: AddYears(x,y) => Date(Year(x)+y,Month(x),Day(x))
    if (arity==2 && instr.m_content=="AddYears") {
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
      instr.m_content="Date";
      child.push_back(instr);
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content="(";
      child.push_back(instr);
      //
      auto const &date=stack[size_t(static_cast<int>(numElt)-2)];
      auto const &addYear=stack.back();
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
      instr.m_content="Year";
      child.push_back(instr);
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content="(";
      child.push_back(instr);
      child.insert(child.end(), date.begin(), date.end());
      instr.m_content="+";
      child.push_back(instr);
      child.insert(child.end(), addYear.begin(), addYear.end());
      instr.m_content=")";
      child.push_back(instr);

      instr.m_content=";";
      child.push_back(instr);

      instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
      instr.m_content="Month";
      child.push_back(instr);
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content="(";
      child.push_back(instr);
      child.insert(child.end(), date.begin(), date.end());
      instr.m_content=")";
      child.push_back(instr);

      instr.m_content=";";
      child.push_back(instr);

      instr.m_type=MWAWCellContent::FormulaInstruction::F_Function;
      instr.m_content="Day";
      child.push_back(instr);
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content="(";
      child.push_back(instr);
      child.insert(child.end(), date.begin(), date.end());
      instr.m_content=")";
      child.push_back(instr);

      //
      instr.m_content=")";
      child.push_back(instr);

      stack.resize(size_t(static_cast<int>(numElt)-arity+1));
      stack[size_t(static_cast<int>(numElt)-arity)] = child;
      continue;
    }
    if ((instr.m_content[0] >= 'A' && instr.m_content[0] <= 'Z') || instr.m_content[0] == '(') {
      if (instr.m_content[0] != '(')
        child.push_back(instr);

      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      instr.m_content="(";
      child.push_back(instr);
      for (int i = 0; i < arity; i++) {
        if (i) {
          instr.m_content=";";
          child.push_back(instr);
        }
        auto const &node=stack[size_t(static_cast<int>(numElt)-arity+i)];
        child.insert(child.end(), node.begin(), node.end());
      }
      instr.m_content=")";
      child.push_back(instr);

      stack.resize(size_t(static_cast<int>(numElt)-arity+1));
      stack[size_t(static_cast<int>(numElt)-arity)] = child;
      continue;
    }
    if (arity==1) {
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      stack[numElt-1].insert(stack[numElt-1].begin(), instr);
      continue;
    }
    if (arity==2) {
      instr.m_type=MWAWCellContent::FormulaInstruction::F_Operator;
      stack[numElt-2].push_back(instr);
      stack[numElt-2].insert(stack[numElt-2].end(), stack[numElt-1].begin(), stack[numElt-1].end());
      stack.resize(numElt-1);
      continue;
    }
    ok=false;
    error = "### unexpected arity";
  }
  pos=input->tell();
  if (pos!=endPos || !ok || stack.size()!=1 || stack[0].empty()) {
    MWAW_DEBUG_MSG(("WingzParser::readFormula: can not read a formula\n"));
    ascii().addDelimiter(pos, '|');
    input->seek(endPos, librevenge::RVNG_SEEK_SET);

    for (auto const &i : stack) {
      for (auto const &j : i)
        f << j << ",";
    }
    if (!error.empty())
      f << error;
    else
      f << "##unknownError,";
    ascii().addPos(debPos);
    ascii().addNote(f.str().c_str());
    return true;
  }

  m_state->m_spreadsheet.m_formulaMap[id]=stack[0];
  for (auto i : stack[0])
    f << i;
  f << ",";
  ascii().addPos(debPos);
  ascii().addNote(f.str().c_str());
  return true;
}

// column/row dim
bool WingzParser::readSpreadsheetSize()
{
  MWAWInputStreamPtr input = getInput();
  int const vers=version();
  long pos=input->tell();
  auto type=static_cast<int>(input->readULong(1));
  if (type!=1 && type!=2) return false;
  libmwaw::DebugStream f;
  f << "Entries(SheetSize)[" << (type==1 ? "col" : "row") << "]:";
  auto val=static_cast<int>(input->readULong(1));
  if (val!=0x80) f << "fl=" << std::hex << val << std::dec << ",";
  auto dSz= static_cast<int>(input->readULong(2));
  if (dSz%4 || !input->checkPosition(pos+(vers==1 ? 4 : 6)+dSz)) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetSize: find bad size for data\n"));
    return false;
  }
  if (vers>1) {
    auto id=static_cast<int>(input->readLong(2));
    if (id) f << "id=" << id << ",";
  }
  f << "pos=[";
  std::vector<float> &dimList=type==1 ? m_state->m_spreadsheet.m_widthCols :
                              m_state->m_spreadsheet.m_heightRows;
  for (int i=0; i<dSz/4; ++i) {
    auto cell=static_cast<int>(input->readULong(2)); // the row/col number
    float dim=float(input->readULong(2))/20.f;  // in TWIP
    if (cell==0xFFFF) f << "-inf";
    else if (cell==0x7FFF) {
      if (type==1) m_state->m_spreadsheet.m_widthDefault=dim;
      else m_state->m_spreadsheet.m_heightDefault=dim;
      f << "inf";
    }
    else {
      if (cell < int(dimList.size()) || cell > int(dimList.size())+1000) {
        MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetSize: the cell seems bad\n"));
        f << "###";
      }
      else
        dimList.resize(size_t(cell)+1, dim);
      f << cell;
    }
    f << ":" << dim << "pt,";
  }
  f << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  return true;
}

// page break pos
bool WingzParser::readSpreadsheetPBreak()
{
  MWAWInputStreamPtr input = getInput();
  int const vers=version();
  long pos=input->tell();
  auto type=static_cast<int>(input->readULong(1));
  if (type!=18 && type!=19) return false;
  libmwaw::DebugStream f;
  f << "Entries(SheetPbrk)[" << (type==18 ? "col" : "row") << "]:";
  auto val=static_cast<int>(input->readULong(1));
  if (val!=0x80) f << "fl=" << std::hex << val << std::dec << ",";
  auto dSz= static_cast<int>(input->readULong(2));
  if (dSz%4 || !input->checkPosition(pos+(vers==1 ? 4 : 6)+dSz)) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetPBreak: find bad size for data\n"));
    return false;
  }
  if (vers==2) {
    auto id=static_cast<int>(input->readLong(2));
    if (id) f << "id=" << id << ",";
  }
  f << "pos=[";
  for (int i=0; i<dSz/4; ++i) {
    auto cell=static_cast<int>(input->readULong(2)); // the row/col number
    if (cell==0xFFFF) f << "-inf";
    else if (cell==0x7FFF) f << "inf";
    else f << cell;
    f << "[sz=" << input->readULong(2) << "],"; // num row/column
  }
  f << "],";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  return true;
}

// unknown zone
bool WingzParser::readSpreadsheetZone5()
{
  MWAWInputStreamPtr input = getInput();
  long pos=input->tell();
  auto type=static_cast<int>(input->readULong(1));
  if (type!=5) return false;
  auto val=static_cast<int>(input->readULong(1));
  auto dSz= static_cast<int>(input->readULong(2));
  long endPos=pos+6+dSz;
  auto id=static_cast<int>(input->readLong(2));

  libmwaw::DebugStream f;
  f << "Entries(ZSheet5)[" << id << "]:";
  if (val!=0x40) f << "fl=" << std::hex << val << std::dec << ",";
  if (dSz<2 || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetZone5: find bad size for data\n"));
    return false;
  }
  val=static_cast<int>(input->readULong(2));
  if (val!=dSz) f << "#dSz=" << val << ",";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  while (!input->isEnd()) {
    pos=input->tell();
    if (pos >= endPos) break;
    type=static_cast<int>(input->readLong(1));
    bool ok=true;
    f.str("");
    f << "ZSheet5-" << std::hex << type << std::dec << ":";
    switch (type) {
    case 0: // nothing
    case 0x4:
      break;
    case 0x3:
      if (pos+4>endPos) {
        ok=false;
        break;
      }
      input->seek(pos+4, librevenge::RVNG_SEEK_SET);
      break;
    case 0x5: // the row?
      if (pos+5>endPos) {
        ok=false;
        break;
      }
      input->seek(pos+5, librevenge::RVNG_SEEK_SET);
      break;
    case 0x1:
    case 0x2:
      if (pos+3>endPos) {
        ok=false;
        break;
      }
      input->seek(pos+3, librevenge::RVNG_SEEK_SET);
      break;
    default:
      ok=false;
      break;
    }
    if (!ok) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      break;
    }
    ascii().addPos(pos);
    ascii().addNote(f.str().c_str());
  }
  pos=input->tell();
  if (pos==endPos)
    return true;
  MWAW_DEBUG_MSG(("WingzParser::readSpreadsheetZone5: find some extra data\n"));
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  ascii().addPos(pos);
  ascii().addNote("ZSheet5-end:###");
  return true;
}

// retrieve a next spreadheet zone (used when parsing stop for some problem )
bool WingzParser::findNextZone(int lastType)
{
  MWAWInputStreamPtr input = getInput();
  bool lastCheck=true;
  while (!input->isEnd()) {
    long pos=input->tell();
    if (!input->checkPosition(pos+8))
      return false;
    auto val=static_cast<int>(input->readULong(2));
    int type=val&0xFF;
    if (type==0x80) {
      if (!lastCheck) {
        input->seek(-3, librevenge::RVNG_SEEK_CUR);
        lastCheck=true;
      }
      continue;
    }
    lastCheck=false;
    if ((val&0xff00)!=0x8000 || (lastType==0 && type!=1) || type>=0x14 || type<lastType)
      continue;
    auto dSz=long(input->readULong(2));
    if (type==0xc) dSz+=4;
    else if (type==0x10) dSz+=4;
    else if (type==0xe) {
      if (dSz < 0x80) dSz += 2;
      else if (input->checkPosition(pos+0x40)) {
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        return true;
      }
      else {
        input->seek(pos+2, librevenge::RVNG_SEEK_SET);
        continue;
      }
    }
    if (input->checkPosition(pos+6+dSz+2)) {
      input->seek(pos+6+dSz+1,librevenge::RVNG_SEEK_SET);
      val=static_cast<int>(input->readULong(1));
      if ((val&0xC0) && !(val&0x3F)) {
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        return true;
      }
    }
    input->seek(pos+2, librevenge::RVNG_SEEK_SET);
  }
  return false;
}

////////////////////////////////////////////////////////////
// macros
////////////////////////////////////////////////////////////
bool WingzParser::readMacro()
{
  MWAWInputStreamPtr input = getInput();
  long pos = input->tell();
  if (!input->checkPosition(pos+76)) {
    MWAW_DEBUG_MSG(("WingzParser::readMacro: the zone seems too short\n"));
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }

  libmwaw::DebugStream f;
  f << "Entries(Macro):";
  auto textSize=long(input->readULong(4));
  f << "textSize=" << std::hex << textSize << std::dec << ",";
  auto scriptSize=long(input->readULong(4));
  f << "scriptSize=" << std::hex << scriptSize << std::dec << ",";
  for (int i=0; i<3; ++i) {
    auto sz=long(input->readULong(4));
    if (sz!=scriptSize)
      f << "sel" << i << "=" << std::hex << sz << std::dec << ",";
  }
  for (int i=0; i<28; ++i) { // f22=f24=0|1|2
    auto val=static_cast<int>(input->readLong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());

  pos=input->tell();
  if (!scriptSize || !input->checkPosition(pos+scriptSize)) {
    MWAW_DEBUG_MSG(("WingzParser::readMacro: the script size seems bad\n"));
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f.str("");
  f << "Macro[script]:";
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+scriptSize, librevenge::RVNG_SEEK_SET);

  pos=input->tell();
  if (!input->checkPosition(pos+textSize)) {
    MWAW_DEBUG_MSG(("WingzParser::readMacro: the text size seems bad\n"));
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    return false;
  }
  f.str("");
  f << "Macro[text]:";
  std::string text("");
  for (long i=0; i<textSize; ++i) text+=char(input->readULong(1));
  f << text;
  ascii().addPos(pos);
  ascii().addNote(f.str().c_str());
  input->seek(pos+textSize, librevenge::RVNG_SEEK_SET);
  return true;
}

////////////////////////////////////////////////////////////
// decode an encrypted file
////////////////////////////////////////////////////////////
bool WingzParser::decodeEncrypted()
{
  MWAWInputStreamPtr input = getInput();
  long length=input->size();
  if (length<=13) {
    MWAW_DEBUG_MSG(("WingzParser::decodeEncrypted: the file seems too short\n"));
    return false;
  }
  input->seek(0, librevenge::RVNG_SEEK_SET);
  unsigned long read;
  uint8_t const *data=input->read(size_t(length), read);
  if (!data || length!=long(read)) {
    MWAW_DEBUG_MSG(("WingzParser::decodeEncrypted: can not read the buffer\n"));
    return false;
  }
  std::unique_ptr<uint8_t[]> buffer{new uint8_t[length]};
  if (!buffer) {
    MWAW_DEBUG_MSG(("WingzParser::decodeEncrypted: can not allocate a buffer\n"));
    return false;
  }
  // the first 12 bytes encoded
  for (size_t i=0; i<12; i++) buffer[i]=data[i];
  // reset the crypt flag to avoid problem
  buffer[12]=0;
  // the next data are encrypted using a basic xor method...
  int delta=0;
  for (long i=13; i<length; ++i) {
    uint8_t const codeString[]= { 0x53, 0x66, 0xA5, 0x35, 0x5A, 0xAA, 0x55, 0xE3 };
    auto v=uint8_t((codeString[(delta&7)]+delta)&0xFF);
    buffer[size_t(i)]=(data[i]^v);
    delta++;
  }

  // finally replace the actual input with a new input
  std::shared_ptr<librevenge::RVNGInputStream> newInput
  (new MWAWStringStream(buffer.get(), static_cast<unsigned int>(length)));
  getParserState()->m_input.reset(new MWAWInputStream(newInput, false));
  return true;
}

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

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

  int const headerSize=13;
  if (!input->checkPosition(headerSize)) {
    MWAW_DEBUG_MSG(("WingzParser::checkHeader: file is too short\n"));
    return false;
  }
  input->seek(0,librevenge::RVNG_SEEK_SET);
  int values[4];
  for (auto &val : values) val=static_cast<int>(input->readULong(2));
  bool isWingz=true;
  if (values[0]==0x574e && values[1]==0x475a && values[2]==0x575a && values[3]==0x5353) // WNGZWZSS
    isWingz=true;
  else if (values[0]==0x4241 && values[1]==0x545F && values[2]==0x4254 && values[3]==0x5353) // BAT_BTSS
    isWingz=false;
  else
    return false;
  setVersion(isWingz ? 2 : 1);
  input->setReadInverted(true);
  libmwaw::DebugStream f;
  f << "FileHeader:";
  std::string name(""); // 0110: version number
  for (int i=0; i<4; ++i)
    name += char(input->readULong(1));
  f << "vers=" << name << ",";
  auto val=static_cast<int>(input->readLong(1));
  if (val==1) { // SgB
    MWAW_DEBUG_MSG(("WingzParser::checkHeader: Find an encrypted file...\n"));
    m_state->m_encrypted=true;
  }
  else if (val) {
    MWAW_DEBUG_MSG(("WingzParser::checkHeader: Find unknown encryped flag...\n"));
    return false;
  }
  ascii().addPos(0);
  ascii().addNote(f.str().c_str());
  if (header)
    header->reset(isWingz ? MWAWDocument::MWAW_T_WINGZ : MWAWDocument::MWAW_T_CLARISRESOLVE, 1, MWAWDocument::MWAW_K_SPREADSHEET);
  input->seek(12,librevenge::RVNG_SEEK_SET);
  input->setReadInverted(false);
  return true;
}

////////////////////////////////////////////////////////////
// read the print info
////////////////////////////////////////////////////////////
bool WingzParser::readPrintInfo()
{
  MWAWInputStreamPtr input = getInput();
  int const vers=version();
  long pos = input->tell();
  auto type=static_cast<int>(input->readULong(1));
  if (type!=0x10) return false;
  auto val=static_cast<int>(input->readULong(1));
  auto dSz=static_cast<int>(input->readULong(2));
  int id=vers==1 ? 0 : static_cast<int>(input->readULong(2));
  int expectedSize=vers==1 ? 0x8a : 0x7c;
  long endPos=pos+(vers==1 ? 4+0x8a : 20+0x7c);
  if (dSz!=expectedSize || !input->checkPosition(endPos)) {
    MWAW_DEBUG_MSG(("WingzParser::readPrintInfo: the header seem bad\n"));
    return false;
  }
  libmwaw::DebugStream f;
  f << "Entries(PrintInfo):";
  if (val!=0x80) f << "fl=" << std::hex << val << std::dec << ",";
  if (id) f << "id=" << id << ",";
  for (int i=0; i<3; ++i) {
    int dim[2];
    for (auto &d: dim) d=static_cast<int>(input->readULong(2));
    if (i==2)
      f << "unit=" << dim[0] << "x" << dim[1] << ",";
    else
      f << "dim" << i << "=" << dim[0] << "x" << dim[1] << ",";
  }
  // 3 small number 0x78,4,6|7
  for (int i=0; i<3; ++i) {
    val=static_cast<int>(input->readULong(2));
    if (val) f << "f" << i << "=" << val << ",";
  }
  // print info
  libmwaw::PrinterInfo info;
  input->setReadInverted(false);
  bool ok=info.read(input);
  input->setReadInverted(true);
  if (!ok) return false;
  f << info;

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

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

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

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

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

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

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

////////////////////////////////////////////////////////////
// send spreadsheet
////////////////////////////////////////////////////////////
bool WingzParser::sendSpreadsheet()
{
  MWAWSpreadsheetListenerPtr listener=getSpreadsheetListener();
  MWAWInputStreamPtr &input= getInput();
  if (!listener) {
    MWAW_DEBUG_MSG(("WingzParser::sendSpreadsheet: I can not find the listener\n"));
    return false;
  }
  auto &sheet = m_state->m_spreadsheet;
  listener->openSheet(sheet.convertInPoint(sheet.m_widthCols), librevenge::RVNG_POINT, std::vector<int>(), sheet.m_name);
  m_graphParser->sendPageGraphics();

  int prevRow = -1;
  for (auto cell : sheet.m_cells) {
    if (cell.position()[1]>prevRow+1) {
      while (cell.position()[1] > prevRow+1) {
        if (prevRow != -1) listener->closeSheetRow();
        int numRepeat;
        float h=sheet.getRowHeight(prevRow+1, numRepeat);
        if (cell.position()[1]<prevRow+1+numRepeat)
          numRepeat=cell.position()[1]-1-prevRow;
        listener->openSheetRow(h, librevenge::RVNG_POINT, numRepeat);
        prevRow+=numRepeat;
      }
    }
    if (cell.position()[1] > prevRow) {
      if (prevRow != -1) listener->closeSheetRow();
      listener->openSheetRow(sheet.getRowHeight(++prevRow), librevenge::RVNG_POINT);
    }
    sheet.update(cell);
    listener->openSheetCell(cell, cell.m_content);
    if (cell.m_content.m_textEntry.valid()) {
      listener->setFont(cell.getFont());
      input->seek(cell.m_content.m_textEntry.begin(), librevenge::RVNG_SEEK_SET);
      while (!input->isEnd() && input->tell()<cell.m_content.m_textEntry.end()) {
        auto c=static_cast<unsigned char>(input->readULong(1));
        if (c==0xd)
          listener->insertEOL();
        else
          listener->insertCharacter(c);
      }
    }
    listener->closeSheetCell();
  }
  if (prevRow!=-1) listener->closeSheetRow();
  listener->closeSheet();
  return true;
}

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