/* -*- 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 <map>
#include <set>
#include <sstream>
#include <stack>

#include <librevenge/librevenge.h>

#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWListener.hxx"
#include "MWAWParagraph.hxx"
#include "MWAWPosition.hxx"
#include "MWAWSubDocument.hxx"

#include "RagTime5ClusterManager.hxx"
#include "RagTime5Parser.hxx"
#include "RagTime5StructManager.hxx"
#include "RagTime5StyleManager.hxx"

#include "RagTime5Layout.hxx"

#include "libmwaw_internal.hxx"

/** Internal: the structures of a RagTime5Layout */
namespace RagTime5LayoutInternal
{
//! the layout cluster ( 4001 zone)
struct ClusterLayout final : public RagTime5ClusterManager::Cluster {
  //! constructor
  ClusterLayout()
    : Cluster(C_Layout)
    , m_zoneList()
    , m_pipelineLink()
    , m_listItemLink()
    , m_childIdSet()
    , m_numChild(0)
    , m_numMasterChild(0)
  {
  }
  //! destructor
  ~ClusterLayout() final;

  //! a zone of in a layout
  struct Zone {
    //! constructor
    Zone()
      : m_mainId(0)
      , m_masterId(0)
      , m_dimension()
    {
    }
    //! the main zone id
    int m_mainId;
    //! the master zone id or 0
    int m_masterId;
    //! the dimension
    MWAWVec2f m_dimension;
  };

  //! list of zone's
  std::vector<Zone> m_zoneList;
  //! link to a pipeline cluster list
  RagTime5ClusterManager::Link m_pipelineLink;
  //! link to  a zone of fieldSize 8(unknown)
  RagTime5ClusterManager::Link m_listItemLink;
  //! list of child id
  std::set<int> m_childIdSet;
  //! the number of classic child
  int m_numChild;
  //! the number of master child
  int m_numMasterChild;
};

ClusterLayout::~ClusterLayout()
{
}

////////////////////////////////////////
//! Internal: the state of a RagTime5Layout
struct State {
  //! constructor
  State()
    : m_numPages(-1)
    , m_idLayoutMap()
    , m_masterIdSet()
    , m_layoutIdToSendList()
  {
  }
  //! the number of pages
  int m_numPages;
  //! map data id to text zone
  std::map<int, std::shared_ptr<ClusterLayout> > m_idLayoutMap;
  //! the list of master id
  std::set<int> m_masterIdSet;
  //! the list of layout to send
  std::vector<int> m_layoutIdToSendList;
};

}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
RagTime5Layout::RagTime5Layout(RagTime5Parser &parser)
  : m_mainParser(parser)
  , m_structManager(m_mainParser.getStructManager())
  , m_parserState(parser.getParserState())
  , m_state(new RagTime5LayoutInternal::State)
{
}

RagTime5Layout::~RagTime5Layout()
{
}

int RagTime5Layout::version() const
{
  return m_parserState->m_version;
}

int RagTime5Layout::numPages() const
{
  if (m_state->m_numPages<0)
    const_cast<RagTime5Layout *>(this)->updateLayouts();
  return m_state->m_numPages;
}

bool RagTime5Layout::sendPageContents()
{
  int page=0;
  for (size_t i=0; i<m_state->m_layoutIdToSendList.size(); ++i) {
    int lId=m_state->m_layoutIdToSendList[i];
    if (m_state->m_idLayoutMap.find(lId)==m_state->m_idLayoutMap.end() || !m_state->m_idLayoutMap.find(lId)->second) {
      MWAW_DEBUG_MSG(("RagTime5Layout::sendPageContents: can not find layout %d\n", lId));
      continue;
    }
    auto &layout=*m_state->m_idLayoutMap.find(lId)->second;
    layout.m_isSent=true;
    for (auto &zone : layout.m_zoneList) {
      MWAWPosition position(MWAWVec2f(0,0), MWAWVec2f(100,100), librevenge::RVNG_POINT);
      position.m_anchorTo=MWAWPosition::Page;
      position.setPage(++page);
      if (zone.m_masterId) {
        if (m_state->m_idLayoutMap.find(zone.m_masterId)==m_state->m_idLayoutMap.end() || !m_state->m_idLayoutMap.find(zone.m_masterId)->second) {
          MWAW_DEBUG_MSG(("RagTime5Layout::sendPageContents: can not find layout %d\n", zone.m_masterId));
        }
        else {
          auto &master=*m_state->m_idLayoutMap.find(zone.m_masterId)->second;
          int cId=0;
          size_t n=layout.m_zoneList.size();
          if (master.m_zoneList.size()==1)
            cId=master.m_zoneList[0].m_mainId;
          else if (n<master.m_zoneList.size())
            cId=master.m_zoneList[n].m_mainId;
          if (cId)
            m_mainParser.send(cId, MWAWListenerPtr(), position);
        }
      }
      if (zone.m_mainId)
        m_mainParser.send(zone.m_mainId, MWAWListenerPtr(), position);
    }
  }
  return true;
}

////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
void RagTime5Layout::updateLayouts()
{
  for (auto it : m_state->m_idLayoutMap) {
    if (!it.second) continue;
    updateLayout(*it.second);
  }
  // look for no master layout
  int nPages=0;
  std::multimap<int,int> numZonesToLayout;
  for (auto it : m_state->m_idLayoutMap) {
    if (!it.second || it.second->m_zoneList.empty() || m_state->m_masterIdSet.find(it.first)!=m_state->m_masterIdSet.end()) {
      if (it.second)
        it.second->m_isSent=true;
      continue;
    }
    numZonesToLayout.insert(std::multimap<int,int>::value_type(it.second->m_numChild,it.first));
    nPages+=static_cast<int>(it.second->m_zoneList.size());
  }
  m_state->m_numPages=nPages;

  for (auto lIt=numZonesToLayout.end(); lIt!=numZonesToLayout.begin();)
    m_state->m_layoutIdToSendList.push_back((--lIt)->second);
}

void RagTime5Layout::updateLayout(RagTime5LayoutInternal::ClusterLayout &layout)
{
  int numChild=0, numMasterChild=0;
  for (auto &zone : layout.m_zoneList) {
    if (zone.m_mainId) {
      if (m_mainParser.getClusterType(zone.m_mainId)==RagTime5ClusterManager::Cluster::C_GraphicZone)
        ++numChild;
      else {
        MWAW_DEBUG_MSG(("RagTime5Layout::updateLayout: find unexpected type for cluster %d\n", zone.m_mainId));
        zone.m_mainId=0;
      }
    }
    if (zone.m_masterId) {
      if (m_mainParser.getClusterType(zone.m_masterId)==RagTime5ClusterManager::Cluster::C_Layout) {
        m_state->m_masterIdSet.insert(zone.m_masterId);
        ++numMasterChild;
      }
      else {
        MWAW_DEBUG_MSG(("RagTime5Layout::updateLayout: find unexpected type for cluster %d\n", zone.m_masterId));
        zone.m_masterId=0;
      }
    }
  }
  layout.m_numChild=numChild;
  layout.m_numMasterChild=numMasterChild;
}


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

////////////////////////////////////////////////////////////
// interface send function
////////////////////////////////////////////////////////////
void RagTime5Layout::flushExtra()
{
  MWAW_DEBUG_MSG(("RagTime5Layout::flushExtra: not implemented\n"));
}

bool RagTime5Layout::send(RagTime5LayoutInternal::ClusterLayout &/*cluster*/, MWAWListenerPtr listener, int /*page*/)
{
  if (!listener)
    listener=m_parserState->getMainListener();
  if (!listener) {
    MWAW_DEBUG_MSG(("RagTime5Layout::send: can not find the listener\n"));
    return false;
  }
  static bool first=true;
  if (first) {
    first=false;
    MWAW_DEBUG_MSG(("RagTime5Layout::send: sorry not implemented\n"));
  }
  return true;
}

////////////////////////////////////////////////////////////
// cluster parser
////////////////////////////////////////////////////////////

namespace RagTime5LayoutInternal
{
//! Internal: the helper to read a clustList
struct ClustListParser final : public RagTime5StructManager::DataParser {
  //! constructor
  ClustListParser(RagTime5ClusterManager &clusterManager, int fieldSize, std::string const &zoneName)
    : RagTime5StructManager::DataParser(zoneName)
    , m_fieldSize(fieldSize)
    , m_linkList()
    , m_clusterManager(clusterManager)
  {
    if (m_fieldSize<4) {
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::ClustListParser: bad field size\n"));
      m_fieldSize=0;
    }
  }
  //! destructor
  ~ClustListParser() final;
  //! returns the not null list dataId list
  std::vector<int> getIdList() const
  {
    std::vector<int> res;
    for (auto const &lnk : m_linkList) {
      if (lnk.m_dataId>0)
        res.push_back(lnk.m_dataId);
    }
    return res;
  }
  //! return the cluster name
  std::string getClusterName(int id) const
  {
    return m_clusterManager.getClusterName(id);
  }

  //! try to parse a data
  bool parseData(MWAWInputStreamPtr &input, long endPos, RagTime5Zone &/*zone*/, int /*n*/, libmwaw::DebugStream &f) final
  {
    long pos=input->tell();
    if (endPos-pos!=m_fieldSize) {
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::ClustListParser::parse: bad data size\n"));
      return false;
    }
    std::vector<int> listIds;
    if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::ClustListParser::parse: can not read an cluster id\n"));
      f << "##clusterIds,";
      return false;
    }
    RagTime5StructManager::ZoneLink link;
    link.m_dataId=listIds[0];
    if (listIds[0])
      f << getClusterName(listIds[0]) << ",";
    f << link;
    m_linkList.push_back(link);
    return true;
  }

  //! the field size
  int m_fieldSize;
  //! the list of read cluster
  std::vector<RagTime5StructManager::ZoneLink> m_linkList;
private:
  //! the main zone manager
  RagTime5ClusterManager &m_clusterManager;
  //! copy constructor, not implemented
  ClustListParser(ClustListParser &orig) = delete;
  //! copy operator, not implemented
  ClustListParser &operator=(ClustListParser &orig) = delete;
};

ClustListParser::~ClustListParser()
{
}

//
//! low level: parser of layout cluster
//
struct LayoutCParser final : public RagTime5ClusterManager::ClusterParser {
  //! constructor
  LayoutCParser(RagTime5ClusterManager &parser, int zoneType)
    : ClusterParser(parser, zoneType, "ClustLayout"), m_cluster(new ClusterLayout)
    , m_actualZoneId(0)
    , m_numZones(0)
    , m_what(-1)
    , m_linkId(-1)
    , m_fieldName("")
    , m_actualZone()
  {
  }
  //! destructor
  ~LayoutCParser() final;
  //! return the current cluster
  std::shared_ptr<RagTime5ClusterManager::Cluster> getCluster() final
  {
    return m_cluster;
  }
  //! return the current layout cluster
  std::shared_ptr<ClusterLayout> getLayoutCluster()
  {
    return m_cluster;
  }
  //! start a new zone
  void startZone() final
  {
    if (m_what<=0)
      ++m_what;
    else if (m_what==1) {
      if (++m_actualZoneId>=m_numZones)
        ++m_what;
      m_actualZone=ClusterLayout::Zone();
    }
  }
  //! end of a start zone call
  void endZone() final
  {
    if (m_what==1)
      m_cluster->m_zoneList.push_back(m_actualZone);
    if (m_link.empty())
      return;
    switch (m_linkId) {
    case 0:
      m_cluster->m_listItemLink=m_link;
      break;
    case 1:
      m_cluster->m_pipelineLink=m_link;
      break;
    case 2:
      m_cluster->m_settingLinks.push_back(m_link);
      break;
    case 3:
      if (m_cluster->m_nameLink.empty())
        m_cluster->m_nameLink=m_link;
      else {
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::endZone: oops the name link is already set\n"));
        m_cluster->m_linksList.push_back(m_link);
      }
      break;
    default:
      m_cluster->m_linksList.push_back(m_link);
      break;
    }
  }
  //! parse a zone
  bool parseZone(MWAWInputStreamPtr &input, long fSz, int N, int flag, libmwaw::DebugStream &f) final
  {
    m_fieldName="";
    m_linkId=-1;
    if (m_what==0)
      return parseHeaderZone(input,fSz,N,flag,f);
    if (m_what==1)
      return parseZoneBlock(input,fSz,N,flag,f);
    m_what=2;

    if (N<0) {
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: N value seems bad\n"));
      f << "###N=" << N << ",";
      return true;
    }
    int val;
    m_link.m_N=N;
    switch (fSz) {
    case 28:
    case 29:
    case 30:
    case 32: {
      std::string mess;
      long linkValues[4];
      if (!readLinkHeader(input, fSz, m_link, linkValues, mess)) {
        f << "###fType=" << std::hex << m_link.m_fileType[0] << std::dec << ",";
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: the field fSz28... type seems bad\n"));
        return true;
      }
      f << m_link << "," << mess;
      long expectedFileType1=0;
      if (m_link.m_fileType[0]==0x35800) { // fSz=28
        m_what=4;
        m_fieldName="zone:longs";
      }
      else if (m_link.m_fileType[0]==0x3e800) { // fSz=28
        m_what=4;
        m_fieldName="list:longs0";
      }
      else if (m_link.m_fileType[0]==0x3c052) {
        m_what=4;
        m_fieldName="list:longs2";
        expectedFileType1=0x50;
      }
      else if (m_link.m_fileType[0]==0x14b9800) { // fSz=30
        m_linkId=0;
        m_what=3;
        m_fieldName="data0";
        expectedFileType1=0x10;
      }
      else if (m_link.m_fileType[0]==0x47040) {
        m_what=4;
        m_linkId=2;
        m_link.m_name=m_fieldName="settings";
      }
      else if (m_link.m_fileType[0]==0 && fSz==30) { // fSz=30
        m_linkId=1;
        m_fieldName="pipeline";
      }
      else if (m_link.m_fileType[0]==0 && fSz==32) {
        expectedFileType1=0x200;
        m_linkId=3;
        m_what=4;
        m_link.m_type=RagTime5ClusterManager::Link::L_UnicodeList;
        m_fieldName="unicodeList";
      }
      else {
        f << "###fType=" << std::hex << m_link.m_fileType[0] << std::dec << ",";
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: the field fSz%d... type seems bad\n", int(fSz)));
        return true;
      }
      if (expectedFileType1>=0 && (m_link.m_fileType[1]&0xFFD7)!=expectedFileType1) {
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: fileType1 seems odd[fSz=28...]\n"));
        f << "###fileType1=" << std::hex << m_link.m_fileType[1] << std::dec << ",";
      }
      break;
    }
    case 36: // follow when it exists the fSz=30 zone, no auxilliar data
      m_fieldName="unicode[def]";
      for (int i=0; i<2; ++i) { // always 0
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readULong(4));
      if (val!=0x7d01a) {
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: find unexpected filetype[fSz=36]\n"));
        f << "###fileType=" << std::hex << val << std::dec << ",";
      }
      val=static_cast<int>(input->readLong(4)); // 0 or small number
      if (val) f << "id0=" << val << ",";
      for (int i=0; i<3; ++i) { // f4=10
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i+2 << "=" << val << ",";
      }
      f << "ids=[";
      for (int i=0; i<3; ++i) { // an increasing sequence
        val=static_cast<int>(input->readLong(4));
        if (val)
          f << val << ",";
        else
          f << "_,";
      }
      f << "],";
      break;
    case 38: // in 1 or 2 exemplar, no auxilliar data
      m_fieldName="settings[Def]";
      val=static_cast<int>(input->readULong(4));
      if (val!=0x47040) {
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: find unexpected type[fSz=38]\n"));
        f << "##fileType=" << std::hex << val << std::dec << ",";
      }
      val=static_cast<int>(input->readULong(4)); // 0|1c07007|1492042
      if (val) f << "fileType1=" << std::hex << val << std::dec << ",";
      for (int i=0; i<5; ++i) { // f1=0|8, f4=10
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i << "=" << val << ",";
      }
      f << "ids=[";
      for (int i=0; i<3; ++i) { // small increasing sequence
        val=static_cast<int>(input->readLong(4));
        if (val) f << val << ",";
        else f << "_,";
      }
      f << "],";
      val=static_cast<int>(input->readLong(2)); // always 0
      if (val) f << "f5=" << val << ",";
      break;
    case 54: { // can be in multiple exemplar ~ 1 by zone, no auxilliar data
      m_fieldName="data1";
      for (int i=0; i<2; ++i) { // always 0
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i << "=" << val << ",";
      }
      float dim[2];
      for (auto &d : dim) d=float(input->readLong(4))/65536.f;
      f << "sz=" << MWAWVec2f(dim[0],dim[1]) << ",";

      std::vector<int> listIds;
      long actPos=input->tell();
      if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
        f << "###cluster1,";
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: can not read cluster block[fSz=54]\n"));
        input->seek(actPos+4, librevenge::RVNG_SEEK_SET);
      }
      else if (listIds[0]) { // link to the new cluster e zone ? (can be also 0)
        m_cluster->m_clusterIdsList.push_back(listIds[0]);
        f << "cluster0=" << getClusterName(listIds[0]) << ",";
      }
      for (int i=0; i<7; ++i) { // always 0
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i+2 << "=" << val << ",";
      }
      for (int i=0; i<9; ++i) { // g0=1, g1=1-7, g2=0-d, g3=0-1, g8=2-8
        val=static_cast<int>(input->readLong(2));
        if (val) f << "g" << i << "=" << val << ",";
      }
      break;
    }
    case 60: // unsure
      m_fieldName="data2";
      m_what=5;
      for (int i=0; i<4; ++i) { // f3=1
        val=static_cast<int>(input->readLong(2));
        if (val) f << "f" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readLong(4)); // 0 or 72cc
      if (val) f << "f4=" << std::hex << val << std::dec << ",";
      val=static_cast<int>(input->readULong(4));
      if (val!=0x14b6842) {
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: find unexpected filetype[fSz=60]\n"));
        f << "#fileType1=" << std::hex << val << std::dec << ",";
      }
      for (int i=0; i<7; ++i) { // g4=g6=1
        val=static_cast<int>(input->readLong(2));
        if (val) f << "g" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readULong(4));
      if (val!=0x35800) { // maybe a link here
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: find unexpected filetype2[fSz=60]\n"));
        f << "#fileType2=" << std::hex << val << std::dec << ",";
      }
      for (int i=0; i<4; ++i) { // 0
        val=static_cast<int>(input->readLong(2));
        if (val) f << "h" << i << "=" << val << ",";
      }
      val=static_cast<int>(input->readULong(4));
      if (val!=0x14b4817) {
        MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: find unexpected filetype3[fSz=60]\n"));
        f << "#fileType3=" << std::hex << val << std::dec << ",";
      }
      for (int i=0; i<4; ++i) { // h4=47
        val=static_cast<int>(input->readLong(2));
        if (val) f << "h" << i+4 << "=" << val << ",";
      }
      break;
    default:
      f << "###fSz=" << fSz << ",";
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZone: find unexpected file size\n"));
      break;
    }
    if (!m_fieldName.empty())
      f << m_fieldName << ",";
    return true;
  }
  //! parse a field
  bool parseField(RagTime5StructManager::Field const &field, int /*m*/, libmwaw::DebugStream &f) final
  {
    if (!m_fieldName.empty())
      f << m_fieldName << ",";
    switch (m_what) {
    case 0: // main
      if (field.m_type==RagTime5StructManager::Field::T_FieldList && field.m_fileType==0x14b5815) {
        for (auto const &child : field.m_fieldList) {
          if (child.m_type==RagTime5StructManager::Field::T_LongList && child.m_fileType==0xcf042) {
            // TODO: storeme
            f << "ids=[";
            for (auto val : child.m_longList) {
              if (val==0)
                f << "_,";
              else
                f << val << ",";
            }
            continue;
          }
          MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseField: find unexpected main field\n"));
          f << "###[" << child << "],";
        }
        break;
      }
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseField: find unexpected main field\n"));
      f << "###" << field;
      break;
    case 3: // data0
      if (field.m_type==RagTime5StructManager::Field::T_Unstructured && field.m_fileType==0xce017) {
        // rare, always 2
        f << "unkn="<< field.m_extra << ",";
        break;
      }
      if (field.m_type==RagTime5StructManager::Field::T_Long && field.m_fileType==0xcf817) {
        // a small value between 2a and 61
        f << "f0="<<field.m_longValue[0] << ",";
        break;
      }
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseField: find unexpected data0 field\n"));
      f << "###" << field;
      break;
    case 4: // list
      if (field.m_type==RagTime5StructManager::Field::T_LongList && field.m_fileType==0xce842) {
        f << "pos=[";
        for (auto val : field.m_longList) {
          if (val==0)
            f << "_,";
          else if (val>1000)
            f << std::hex << val << std::dec << ",";
          else
            f << val << ",";
        }
        f << "],";
        m_link.m_longList=field.m_longList;
        break;
      }
      if (field.m_type==RagTime5StructManager::Field::T_LongList && field.m_fileType==0xcf042) { // with 3c052
        f << "unkn=[";
        for (auto val : field.m_longList) {
          if (val==0)
            f << "_,";
          else
            f << val << ",";
        }
        break;
      }
      if (field.m_type==RagTime5StructManager::Field::T_Unstructured && field.m_fileType==0xce017) {
        // a small value 2|4|a|1c|40
        f << "unkn="<<field.m_extra << ",";
        break;
      }
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseField: find unexpected list field\n"));
      f << "###" << field;
      break;
    case 5: // fSz=60
      if (field.m_type==RagTime5StructManager::Field::T_FieldList && field.m_fileType==0x14b4815) {
        for (auto const &child : field.m_fieldList) {
          if (child.m_type==RagTime5StructManager::Field::T_LongList && child.m_fileType==0xce842) {
            f << "unkn=[";
            for (auto val : child.m_longList) {
              if (val==0)
                f << "_,";
              else
                f << val << ",";
            }
            continue;
          }
          MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseField: find unexpected data2 field\n"));
          f << "###[" << child << "],";
        }
        break;
      }
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseField: find unexpected data2 field\n"));
      f << "###" << field;
      break;

    default:
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseField: find unexpected sub field\n"));
      f << "###" << field;
      break;
    }
    return true;
  }
protected:
  //! parse a zone block
  bool parseZoneBlock(MWAWInputStreamPtr &input, long fSz, int N, int flag, libmwaw::DebugStream &f)
  {
    if (N<0 || m_what!=1 || (fSz!=50 && fSz!=66)) {
      f << "###N=" << N << ",fSz=" << fSz << ",";
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZoneBlock: find unexpected main field\n"));
      return false;
    }
    f << "block, fl=" << std::hex << flag << std::dec << ",";
    m_fieldName="block";
    if (N!=1) {
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZoneBlock: zone N seems badA\n"));
      f << "#N=" << N << ",";
    }
    int val;
    for (int i=0; i<2; ++i) { // always 0
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "f" << i << "=" << val << ",";
    }
    float dim[2];
    for (auto &d : dim) d=float(input->readLong(4))/65536.f;
    m_actualZone.m_dimension=MWAWVec2f(dim[0],dim[1]);
    f << "sz=" << MWAWVec2f(dim[0],dim[1]) << ",";
    std::vector<int> listIds;
    long actPos=input->tell();
    if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
      f << "###cluster0,";
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZoneBlock: can not read first cluster block\n"));
      input->seek(actPos+4, librevenge::RVNG_SEEK_SET);
    }
    else if (listIds[0]) { // link to a cluster main zone
      m_actualZone.m_mainId=listIds[0];
      m_cluster->m_childIdSet.insert(listIds[0]);
      f << "cluster0=" << getClusterName(listIds[0]) << ",";
    }
    for (int i=0; i<2; ++i) { // always 0: another item?
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "f" << i+2 << "=" << val << ",";
    }
    listIds.clear();
    actPos=input->tell();
    if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
      f << "###cluster1,";
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseZoneBlock: can not read second cluster block\n"));
      input->seek(actPos+4, librevenge::RVNG_SEEK_SET);
    }
    else if (listIds[0]) { // link to the master layout
      m_actualZone.m_masterId=listIds[0];
      m_cluster->m_childIdSet.insert(listIds[0]);
      f << "cluster1=" << getClusterName(listIds[0]) << ",";
    }
    for (int i=0; i<2; ++i) { // either 0,0 or 1,small number (but does not seems a data id )
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "f" << i+4 << "=" << val << ",";
    }
    val=static_cast<int>(input->readLong(4)); // alwas 1?
    if (val!=1) f << "f6=" << val << ",";
    f << "unkn=[";
    for (int i=0; i<4; ++i) { // small number
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << val << ",";
      else
        f << "_,";
    }
    f << "],";
    for (int i=0; i<2; ++i) { // g1=0|4|5
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "g" << i << "=" << val << ",";
    }
    if (fSz==66) {
      for (int i=0; i<2; ++i) { // g2=0|7|8, g3=5-12
        val=static_cast<int>(input->readLong(4));
        if (val)
          f << "g" << i+2 << "=" << val << ",";
      }
      for (int i=0; i<2; ++i) { // fl0=0|1
        val=static_cast<int>(input->readLong(1));
        if (val)
          f << "fl" << i << "=" << val << ",";
      }
      for (int i=0; i<3; ++i) { // g4=0|1, g6=g4
        val=static_cast<int>(input->readLong(2));
        if (val)
          f << "g" << i+4 << "=" << val << ",";
      }
    }
    return true;
  }

  //! parse the header zone
  bool parseHeaderZone(MWAWInputStreamPtr &input, long fSz, int N, int flag, libmwaw::DebugStream &f)
  {
    f << "header, fl=" << std::hex << flag << std::dec << ",";
    m_fieldName="header";
    if (N!=-5 || m_what!=0 || (fSz!=123 && fSz!=127 && fSz!=128 && fSz!=132)) {
      f << "###N=" << N << ",fSz=" << fSz << ",";
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseHeaderZone: find unexpected main field\n"));
      return false;
    }
    int val;
    for (int i=0; i<2; ++i) { // always 0?
      val=static_cast<int>(input->readLong(2));
      if (val) f << "f" << i+1 << "=" << val << ",";
    }
    val=static_cast<int>(input->readLong(2));
    f << "id=" << val << ",";
    val=static_cast<int>(input->readULong(2));
    if (val!=m_type) {
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseHeaderZone: unexpected zone type\n"));
      f << "##zoneType=" << std::hex << val << std::dec << ",";
    }
    for (int i=0; i<2; ++i) { // f0=0, f1=4-6
      val=static_cast<int>(input->readLong(4));
      if (val)
        f << "f" << i << "=" << val << ",";
    }
    val=static_cast<int>(input->readLong(2)); // always 16
    if (val!=16)
      f << "f2=" << val << ",";
    m_numZones=static_cast<int>(input->readLong(4));
    if (m_numZones!=1)
      f << "num[zone1]=" << m_numZones << ",";
    auto fileType=long(input->readULong(4));
    if (fileType!=0x14b6052) {
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseHeaderZone: find unexpected filetype\n"));
      f << "#fileType0=" << std::hex << fileType << std::dec << ",";
    }
    for (int i=0; i<9; ++i) { // f11=0x60,
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "f" << i+5 << "=" << val << ",";
    }
    val=static_cast<int>(input->readLong(1));
    if (val!=1) f << "fl=" << val << ",";
    if (fSz==128 || fSz==132) {
      for (int i=0; i<5; ++i) { // unsure find only 0 here
        val=static_cast<int>(input->readLong(1));
        if (val)
          f << "flA" << i << "=" << val << ",";
      }
    }
    val=static_cast<int>(input->readLong(4));
    if (val) // 0-20
      f << "g0=" << val << ",";
    long actPos=input->tell();
    std::vector<int> listIds;
    if (!RagTime5StructManager::readDataIdList(input, 1, listIds)) {
      MWAW_DEBUG_MSG(("RagTime5LayoutInternal::LayoutCParser::parseHeaderZone: can not read first cluster block\n"));
      f << "##badCluster,";
      input->seek(actPos+4, librevenge::RVNG_SEEK_SET);
    }
    else if (listIds[0]) { // find link to a named frame cluster
      m_cluster->m_clusterIdsList.push_back(listIds[0]);
      f << "clusterId1=" << getClusterName(listIds[0]) << ",";
    }
    for (int i=0; i<2; ++i) { // always 0
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "g" << i+1 << "=" << val << ",";
    }
    float dim[4];
    for (auto &d : dim) d=float(input->readLong(4))/65536.f;
    MWAWVec2f frameSize(dim[0],dim[1]);
    f << "sz=" << frameSize << ",";
    for (int i=0; i<2; ++i) dim[i]=float(input->readLong(4))/65536.f;
    if (MWAWVec2f(dim[0],dim[1])!=frameSize)
      f << "sz2=" << MWAWVec2f(dim[0],dim[1]) << ",";
    for (int i=0; i<10; ++i) { // find g3=0|1|69, g8=0|5|8, g9=g11=g12=1, g10=small number(but not a data id)
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "g" << i+3 << "=" << val << ",";
    }
    for (auto &d : dim) d=float(input->readLong(4))/65536.f;
    f << "dim=" << MWAWBox2f(MWAWVec2f(dim[0],dim[1]), MWAWVec2f(dim[2],dim[3])) << ",";
    for (int i=0; i<4; ++i) { // h3=0|1|3|6
      val=static_cast<int>(input->readLong(2));
      if (val)
        f << "h" << i << "=" << val << ",";
    }
    if (fSz==127 || fSz==132) {
      for (int i=0; i<2; ++i) { // always 0
        val=static_cast<int>(input->readLong(2));
        if (val)
          f << "h" << i+3 << "=" << val << ",";
      }
    }
    return true;
  }

  //! the current cluster
  std::shared_ptr<ClusterLayout> m_cluster;
  //! the actual zone id
  int m_actualZoneId;
  //! the number of zones
  int m_numZones;
  //! a index to know which field is parsed :  0: main, 1:list of zones, 2: unknown, 3:data0, 4:list, 5: unknown
  int m_what;
  //! the link id : 0: listItem, 1: pipeline, 2: settinglinks, 3: namelink,
  int m_linkId;
  //! the actual field name
  std::string m_fieldName;

  //! the actual zone
  ClusterLayout::Zone m_actualZone;
};

LayoutCParser::~LayoutCParser()
{
}

}

std::shared_ptr<RagTime5ClusterManager::Cluster> RagTime5Layout::readLayoutCluster(RagTime5Zone &zone, int zoneType)
{
  auto clusterManager=m_mainParser.getClusterManager();
  if (!clusterManager) {
    MWAW_DEBUG_MSG(("RagTime5Layout::readLayoutCluster: oops can not find the cluster manager\n"));
    return std::shared_ptr<RagTime5ClusterManager::Cluster>();
  }
  RagTime5LayoutInternal::LayoutCParser parser(*clusterManager, zoneType);
  if (!clusterManager->readCluster(zone, parser) || !parser.getLayoutCluster()) {
    MWAW_DEBUG_MSG(("RagTime5Layout::readLayoutCluster: oops can not find the cluster\n"));
    return std::shared_ptr<RagTime5ClusterManager::Cluster>();
  }

  auto cluster=parser.getLayoutCluster();
  if (!cluster->m_listItemLink.empty())
    m_mainParser.readFixedSizeZone(cluster->m_listItemLink, "LayoutUnknown0");
  if (!cluster->m_pipelineLink.empty() && cluster->m_pipelineLink.m_ids.size()==1) {
    if (cluster->m_pipelineLink.m_fieldSize==4) {
      RagTime5LayoutInternal::ClustListParser listParser(*clusterManager, 4, "LayoutPipeline");
      m_mainParser.readFixedSizeZone(cluster->m_pipelineLink, listParser);
      m_mainParser.checkClusterList(listParser.getIdList());
    }
    else {
      MWAW_DEBUG_MSG(("RagTime5Layout::readClusterLayoutData: find unexpected field size for pipeline data\n"));
      m_mainParser.readFixedSizeZone(cluster->m_pipelineLink, "LayoutPipelineBAD");
    }
  }
  // can have some setting
  for (int wh=0; wh<2; ++wh) {
    auto const &list=wh==0 ? cluster->m_conditionFormulaLinks : cluster->m_settingLinks;
    for (auto const &lnk : list) {
      if (lnk.empty()) continue;
      RagTime5ClusterManager::Cluster unknCluster(RagTime5ClusterManager::Cluster::C_Unknown);
      unknCluster.m_dataLink=lnk;
      RagTime5StructManager::FieldParser defaultParser(wh==0 ? "CondFormula" : "Settings");
      m_mainParser.readStructZone(unknCluster, defaultParser, 0);
    }
  }

  if (!cluster->m_nameLink.empty()) {
    std::map<int, librevenge::RVNGString> idToStringMap;
    m_mainParser.readUnicodeStringList(cluster->m_nameLink, idToStringMap);
  }

  for (auto const &lnk : cluster->m_linksList) {
    if (lnk.m_type==RagTime5ClusterManager::Link::L_List) {
      m_mainParser.readListZone(lnk);
      continue;
    }
    std::stringstream s;
    s << "Layout_Data" << lnk.m_fieldSize;
    RagTime5StructManager::DataParser defaultParser(s.str());
    m_mainParser.readFixedSizeZone(lnk, defaultParser);
  }

  if (m_state->m_idLayoutMap.find(zone.m_ids[0])!=m_state->m_idLayoutMap.end()) {
    MWAW_DEBUG_MSG(("RagTime5Layout::readLayoutCluster: cluster %d already exists\n", zone.m_ids[0]));
  }
  else
    m_state->m_idLayoutMap[zone.m_ids[0]]=cluster;
  return cluster;
}


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