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

#include <librevenge/librevenge.h>

#include "MWAWFont.hxx"
#include "MWAWFontConverter.hxx"
#include "MWAWHeader.hxx"
#include "MWAWParser.hxx"
#include "MWAWPresentationListener.hxx"
#include "MWAWPosition.hxx"

#include "ClarisWksDocument.hxx"
#include "ClarisWksStruct.hxx"
#include "ClarisWksStyleManager.hxx"

#include "ClarisWksPresentation.hxx"

/** Internal: the structures of a ClarisWksPresentation */
namespace ClarisWksPresentationInternal
{
//! Internal the presentation
struct Presentation final : public ClarisWksStruct::DSET {
  // constructor
  explicit Presentation(ClarisWksStruct::DSET const &dset = ClarisWksStruct::DSET())
    : ClarisWksStruct::DSET(dset)
    , m_contentIdList()
    , m_noteIdList()
    , m_thumbnailsIdList()
    , m_titleList()
    , m_masterId(0)
    , m_masterDetached(false)
  {
  }
  //! destructor
  ~Presentation() final;
  //! operator<<
  friend std::ostream &operator<<(std::ostream &o, Presentation const &doc)
  {
    o << static_cast<ClarisWksStruct::DSET const &>(doc);
    return o;
  }
  /** remove a child from a list.

      Normally, this function is not called, so optimizing it is not usefull
   */
  void removeChild(int cId, bool normalChild) final
  {
    DSET::removeChild(cId, normalChild);
    if (m_id+1==cId) m_masterDetached=true;
  }

  //! the list of main zone id
  std::vector<int> m_contentIdList;
  //! the list of notes zone id
  std::vector<int> m_noteIdList;
  //! the list of thumbnail zone id
  std::vector<int> m_thumbnailsIdList;
  //! the list of title
  std::vector<librevenge::RVNGString> m_titleList;
  //! the master zone (background + header/footer)
  int m_masterId;
  //! true if the auxiliary zone is detached
  bool m_masterDetached;
};

Presentation::~Presentation()
{
}

//! Internal: the state of a ClarisWksPresentation
struct State {
  //! constructor
  State()
    : m_presentation()
    , m_presentationMap()
  {
  }
  //! the main presentation
  std::shared_ptr<Presentation> m_presentation;
  //! map zId to presentation
  std::map<int, std::shared_ptr<Presentation> > m_presentationMap;
};

////////////////////////////////////////
//! Internal: the subdocument of a ClarisWksPresentation
class SubDocument final : public MWAWSubDocument
{
public:
  SubDocument(ClarisWksPresentation &pars, MWAWInputStreamPtr const &input, int zoneId)
    : MWAWSubDocument(pars.m_mainParser, input, MWAWEntry())
    , m_presentationParser(&pars)
    , m_id(zoneId) {}

  //! destructor
  ~SubDocument() final {}

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

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

protected:
  //! the subdocument id
  int m_id;
private:
  SubDocument(SubDocument const &orig) = delete;
  SubDocument &operator=(SubDocument const &orig) = delete;
};

void SubDocument::parse(MWAWListenerPtr &listener, libmwaw::SubDocumentType type)
{
  if (!listener || (type==libmwaw::DOC_TEXT_BOX&&!listener->canWriteText())) {
    MWAW_DEBUG_MSG(("ClarisWksPresentationInternal::SubDocument::parse: no listener\n"));
    return;
  }
  if (!m_presentationParser) {
    MWAW_DEBUG_MSG(("ClarisWksPresentationInternal::SubDocument::parse: can not find parser\n"));
    return;
  }
  long pos = m_input->tell();
  m_presentationParser->askToSend(m_id);
  m_input->seek(pos, librevenge::RVNG_SEEK_SET);
}
}

////////////////////////////////////////////////////////////
// constructor/destructor, ...
////////////////////////////////////////////////////////////
ClarisWksPresentation::ClarisWksPresentation(ClarisWksDocument &document)
  : m_document(document)
  , m_parserState(document.m_parserState)
  , m_state(new ClarisWksPresentationInternal::State)
  , m_mainParser(&document.getMainParser())
{
}

ClarisWksPresentation::~ClarisWksPresentation()
{ }

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

int ClarisWksPresentation::numPages() const
{
  if (m_parserState->m_kind!=MWAWDocument::MWAW_K_PRESENTATION || !m_state->m_presentation)
    return 1;
  return int(m_state->m_presentation->m_contentIdList.size());
}

bool ClarisWksPresentation::updatePageSpanList(MWAWPageSpan const &page, std::vector<MWAWPageSpan> &spanList)
{
  if (!m_state->m_presentation)
    return false;
  int numPage=numPages();
  if (numPage<=0)
    return false;
  for (int i=0; i<numPage; ++i) {
    MWAWPageSpan ps(page);
    if (i<static_cast<int>(m_state->m_presentation->m_titleList.size()) &&
        !m_state->m_presentation->m_titleList[size_t(i)].empty())
      ps.setPageName(m_state->m_presentation->m_titleList[size_t(i)]);
    ps.setPageSpan(1);
    spanList.push_back(ps);
  }
  return true;
}

void ClarisWksPresentation::askToSend(int number)
{
  m_document.sendZone(number);
}

////////////////////////////////////////////////////////////
// Intermediate level
////////////////////////////////////////////////////////////
void ClarisWksPresentation::updateSlideTypes() const
{
  auto it = m_state->m_presentationMap.begin();
  while (it != m_state->m_presentationMap.end()) {
    std::shared_ptr<ClarisWksPresentationInternal::Presentation> pres = it++->second;
    if (!pres) continue;
    if (pres->m_id==1)
      m_state->m_presentation=pres;
    for (int step=0; step<3; ++step) {
      auto const &content=step==0 ? pres->m_contentIdList :
                          step==1 ? pres->m_noteIdList : pres->m_thumbnailsIdList;
      int c=0;
      for (auto id : content) {
        auto zone=m_document.getZone(id);
        if (!zone) continue;
        static ClarisWksStruct::DSET::Position const positions[]= {
          ClarisWksStruct::DSET::P_Slide, ClarisWksStruct::DSET::P_SlideNote, ClarisWksStruct::DSET::P_SlideThumbnail
        };
        zone->m_position=positions[step];
        zone->m_page=++c;
      }
    }
    // finally update the background type
    auto zone=m_document.getZone(pres->m_id+1);
    if (!zone || zone->m_fileType!=0) continue;
    zone->m_position=ClarisWksStruct::DSET::P_SlideMaster;
  }
}

void ClarisWksPresentation::disconnectMasterFromContents() const
{
  auto it = m_state->m_presentationMap.begin();
  while (it != m_state->m_presentationMap.end()) {
    auto pres = it++->second;
    if (!pres) continue;
    auto background=m_document.getZone(pres->m_id+1);
    pres->m_masterId=pres->m_id+1;
    if (!background || background->m_fathersList.size()!=1) {
      MWAW_DEBUG_MSG(("ClarisWksPresentation::disconnectMasterFromContents: can not find the background zone\n"));
      continue;
    }
    int masterId=*background->m_fathersList.begin();
    auto master=m_document.getZone(masterId);
    if (!master) {
      MWAW_DEBUG_MSG(("ClarisWksPresentation::disconnectMasterFromContents: can not find the master zone\n"));
      continue;
    }
    master->m_position=ClarisWksStruct::DSET::P_SlideMaster;
    pres->m_masterId=masterId;
    for (int step=0; step<3; ++step) {
      std::vector<int> const &content=step==0 ? pres->m_contentIdList :
                                      step==1 ? pres->m_noteIdList : pres->m_thumbnailsIdList;
      for (auto childId : content) {
        if (master->m_fathersList.find(childId)==master->m_fathersList.end()) {
          MWAW_DEBUG_MSG(("ClarisWksPresentation::disconnectMasterFromContents: find a content zone with no link to master\n"));
          continue;
        }
        std::shared_ptr<ClarisWksStruct::DSET> zone=m_document.getZone(childId);
        if (!zone) continue;
        zone->removeChild(masterId, true);
        master->m_fathersList.erase(childId);
      }
    }
  }
}

////////////////////////////////////////////////////////////
// a document part
////////////////////////////////////////////////////////////
std::shared_ptr<ClarisWksStruct::DSET> ClarisWksPresentation::readPresentationZone
(ClarisWksStruct::DSET const &zone, MWAWEntry const &entry, bool &complete)
{
  complete = true;
  if (!entry.valid() || zone.m_fileType != 5 || entry.length() < 0x40)
    return std::shared_ptr<ClarisWksStruct::DSET>();
  long pos = entry.begin();
  MWAWInputStreamPtr &input= m_parserState->m_input;
  input->seek(pos+8+16, librevenge::RVNG_SEEK_SET); // avoid header+8 generic number
  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;
  std::shared_ptr<ClarisWksPresentationInternal::Presentation>
  presentationZone(new ClarisWksPresentationInternal::Presentation(zone));

  f << "Entries(PresentationDef):" << *presentationZone << ",";
  ascFile.addDelimiter(input->tell(), '|');
  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());

  // read the last part
  long data0Length = zone.m_dataSz;
  long N = zone.m_numData;
  if (entry.length() -8-12 != data0Length*N + zone.m_headerSz) {
    if (data0Length == 0 && N) {
      MWAW_DEBUG_MSG(("ClarisWksPresentation::readPresentationZone: can not find definition size\n"));
      input->seek(entry.end(), librevenge::RVNG_SEEK_SET);
      return std::shared_ptr<ClarisWksStruct::DSET>();
    }

    MWAW_DEBUG_MSG(("ClarisWksPresentation::readPresentationZone: unexpected size for zone definition, try to continue\n"));
  }

  if (m_state->m_presentationMap.find(presentationZone->m_id) != m_state->m_presentationMap.end()) {
    MWAW_DEBUG_MSG(("ClarisWksPresentation::readPresentationZone: zone %d already exists!!!\n", presentationZone->m_id));
  }
  else
    m_state->m_presentationMap[presentationZone->m_id] = presentationZone;

  long dataEnd = entry.end()-N*data0Length;
  input->seek(dataEnd, librevenge::RVNG_SEEK_SET);
  for (int i = 0; i < N; i++) {
    pos = input->tell();

    f.str("");
    f << "PresentationDef-" << i;
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());
    input->seek(pos+data0Length, librevenge::RVNG_SEEK_SET);
  }
  input->seek(entry.end(), librevenge::RVNG_SEEK_SET);

  pos = input->tell();
  bool ok = readZone1(*presentationZone);
  if (ok) {
    pos = input->tell();
    ok = readZone2(*presentationZone);
  }
  if (!ok)
    input->seek(pos, librevenge::RVNG_SEEK_SET);

  return presentationZone;
}

////////////////////////////////////////////////////////////
//
// Intermediate level
//
////////////////////////////////////////////////////////////
bool ClarisWksPresentation::readZone1(ClarisWksPresentationInternal::Presentation &pres)
{
  long val;
  MWAWInputStreamPtr &input= m_parserState->m_input;
  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;

  for (int st = 0; st < 3; st++) {
    long pos = input->tell();
    auto N = long(input->readULong(4));
    long endPos = pos+16*N+4;
    if (N < 0 || !input->checkPosition(endPos)) {
      input->seek(pos, librevenge::RVNG_SEEK_SET);
      MWAW_DEBUG_MSG(("ClarisWksPresentation::readZone1: zone seems too short\n"));
      return false;
    }
    f.str("");
    f << "Entries(PresentationStr)[" << st << "]" << ":N=" << N << ",";
    ascFile.addPos(pos);
    ascFile.addNote(f.str().c_str());

    for (int i = 0; i < N; i++) {
      f.str("");
      f << "PresentationStr" << st << "-" << i << ":";
      pos = input->tell();
      auto zoneId = static_cast<int>(input->readLong(4));
      if (zoneId > 0) {
        if (st == 1)
          pres.m_contentIdList.push_back(zoneId);
        else if (st == 2)
          pres.m_noteIdList.push_back(zoneId);
        else
          pres.m_thumbnailsIdList.push_back(zoneId);
        pres.m_otherChilds.push_back(zoneId);
      }
      else
        f << "###";
      f << "zId=" << zoneId << ",";
      f << "f1=" << input->readLong(4) << ","; // always 8 ?
      auto sSz = static_cast<int>(input->readLong(4));
      long endStringPos=pos+12+sSz;
      if (sSz < 0 || !input->checkPosition(endStringPos+4)) {
        input->seek(pos, librevenge::RVNG_SEEK_SET);
        MWAW_DEBUG_MSG(("ClarisWksPresentation::readZone1: can not read string %d\n", i));
        return false;
      }
      librevenge::RVNGString title("");
      for (int s = 0; s < sSz; s++) {
        auto ch=char(input->readULong(1));
        if (!ch) continue;
        f << ch;
        // checkme: can we always use a defaut font
        int unicode= m_parserState->m_fontConverter->unicode(3, static_cast<unsigned char>(ch));
        if (unicode==-1)
          title.append(ch);
        else
          libmwaw::appendUnicode(static_cast<uint32_t>(unicode), title);
      }
      if (st==1)
        pres.m_titleList.push_back(title);
      input->seek(endStringPos, librevenge::RVNG_SEEK_SET);
      val = input->readLong(4); // always 0 ?
      if (val)
        f << "f2=" << val << ",";
      ascFile.addPos(pos);
      ascFile.addNote(f.str().c_str());
    }
  }

  return true;
}

bool ClarisWksPresentation::readZone2(ClarisWksPresentationInternal::Presentation &/*pres*/)
{
  libmwaw::DebugFile &ascFile = m_parserState->m_asciiFile;
  libmwaw::DebugStream f;

  MWAWInputStreamPtr &input= m_parserState->m_input;
  long pos = input->tell();
  long endPos = pos+16;
  input->seek(endPos, librevenge::RVNG_SEEK_SET);
  if (long(input->tell()) != endPos) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    MWAW_DEBUG_MSG(("ClarisWksPresentation::readZone2: zone seems too short\n"));
    return false;
  }

  input->seek(pos, librevenge::RVNG_SEEK_SET);
  f << "Entries(PresentationTitle):";
  // checkme this also be 1 times : [ 0, f2] or 1, str0, f2, or a mixt
  for (int i = 0; i < 3; i++) { // find f0=1, f1=0, f2=[0|1|2|4]
    long val = input->readLong(4);
    if (val)
      f << "f" << i << "=" << val << ",";
  }
  auto sSz = static_cast<int>(input->readLong(4));
  input->seek(pos+16+sSz, librevenge::RVNG_SEEK_SET);
  if (sSz < 0 || input->tell() != pos+16+sSz) {
    input->seek(pos, librevenge::RVNG_SEEK_SET);
    MWAW_DEBUG_MSG(("ClarisWksPresentation::readZone2: can not read title\n"));
    return false;
  }
  input->seek(pos+16, librevenge::RVNG_SEEK_SET);
  std::string title("");
  for (int s = 0; s < sSz; s++)
    title += char(input->readULong(1));
  f << title << ",";

  ascFile.addPos(pos);
  ascFile.addNote(f.str().c_str());
  return true;
}

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

bool ClarisWksPresentation::sendMaster()
{
  if (!m_state->m_presentation) {
    MWAW_DEBUG_MSG(("ClarisWksPresentation::sendMaster: oops can not find main presentation\n"));
    return false;
  }
  int masterId=m_state->m_presentation->m_masterId;
  return m_document.sendZone(masterId > 0 ? masterId : 2);
}

bool ClarisWksPresentation::sendZone(int number)
{
  if (number != 1) {
    MWAW_DEBUG_MSG(("ClarisWksPresentation::sendZone: sending embeded presentation is not implemented\n"));
    return false;
  }
  auto presentation = m_state->m_presentation;
  MWAWPresentationListenerPtr listener=m_parserState->m_presentationListener;
  if (!presentation || !listener) {
    MWAW_DEBUG_MSG(("ClarisWksPresentation::sendZone: can not find the presentation listener\n"));
    return false;
  }
  presentation->m_parsed = true;
  if (!presentation->m_masterDetached)
    m_document.forceParsed(number+1);
  for (size_t p = 0; p < presentation->m_contentIdList.size(); p++) {
    if (p)
      listener->insertBreak(MWAWListener::PageBreak);

    // first insert the content

    int id = presentation->m_contentIdList[p];
    if (id > 0)
      m_document.sendZone(id);

    // try to retrieve the notes: ie. the text zone in the notes zone
    if (p>=presentation->m_noteIdList.size() || presentation->m_noteIdList[p] <=0)
      continue;

    auto zone=m_document.getZone(presentation->m_noteIdList[p]);
    if (!zone) continue;
    std::vector<int> listTextChild;
    for (auto const &cZone : zone->m_childs) {
      if (cZone.m_type != ClarisWksStruct::DSET::C_Zone)
        continue;
      int cId=cZone.m_id;
      auto child=m_document.getZone(cId);
      if (child && child->m_fileType==1)
        listTextChild.push_back(cId);
    }
    if (listTextChild.size()!=1) {
      MWAW_DEBUG_MSG(("ClarisWksPresentation::sendZone: the number of notes text zone is odd=%d\n",
                      static_cast<int>(listTextChild.size())));
    }
    if (listTextChild.empty())
      continue;
    std::shared_ptr<MWAWSubDocument> doc(new ClarisWksPresentationInternal::SubDocument(*this, m_parserState->m_input, listTextChild[0]));
    // fixme
    MWAWPosition pos(MWAWVec2f(0,400), MWAWVec2f(600,200), librevenge::RVNG_POINT);
    pos.m_anchorTo=MWAWPosition::Page;
    listener->insertSlideNote(pos, doc);
  }
  return true;
}

void ClarisWksPresentation::flushExtra()
{
  std::shared_ptr<MWAWListener> listener=m_parserState->getMainListener();
  if (!listener || listener->getType()!=MWAWListener::Presentation) return;

  for (auto iter : m_state->m_presentationMap) {
    auto presentation = iter.second;
    if (presentation->m_parsed)
      continue;
    listener->insertEOL();
    sendZone(iter.first);
  }
}
// vim: set filetype=cpp tabstop=2 shiftwidth=2 cindent autoindent smartindent noexpandtab:
