/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libe-book project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <algorithm>
#include <cstring>
#include <iterator>
#include <vector>

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/optional.hpp>

#include <librevenge/librevenge.h>

#include "FB2Content.h"
#include "FB2ContentCollector.h"
#include "FB2ContentMap.h"
#include "FB2Style.h"

namespace libebook
{

using boost::optional;

using std::string;

namespace
{

void parseLang(const string &langStr, optional<string> &lang, optional<string> &country, optional<string> &script)
{
  // TODO: xml:lang follows BCP-47, but I do not want to link with liblangtag
  // just to parse the lang string. Let's see if more complicated xml:lang
  // strings are actually used in FictionBook2 files in practice.

  std::vector<string> tags;
  tags.reserve(3);
  boost::split(tags, langStr, boost::is_any_of("-_"), boost::token_compress_off);

  if ((tags.size() > 0) && boost::all(tags[0], boost::is_lower()) && (2 <= tags[0].size()) && (3 >= tags[0].size()))
  {
    lang = tags[0];

    if (tags.size() > 1)
    {
      if (boost::all(tags[1], boost::is_upper()) && (2 == tags[1].size()))
        country = tags[1];
      else
        script = tags[1];
    }

    if ((tags.size() > 2) && bool(script))
    {
      if (boost::all(tags[2], boost::is_upper()) && (2 == tags[2].size()))
        country = tags[2];
    }
  }
}

}

FB2ContentCollector::FB2ContentCollector(
  librevenge::RVNGTextInterface *document, const librevenge::RVNGPropertyList &metadata,
  const FB2ContentMap &notes, const FB2ContentMap &bitmaps)
  : m_document(document)
  , m_metadata(metadata)
  , m_notes(notes)
  , m_bitmaps(bitmaps)
  , m_currentFootnote(1)
  , m_openPara(false)
{
}

void FB2ContentCollector::defineMetadataEntry(const char *, const char *)
{
}

void FB2ContentCollector::openMetadataEntry(const char *)
{
}

void FB2ContentCollector::closeMetadataEntry()
{
}

void FB2ContentCollector::defineID(const char *)
{
}

void FB2ContentCollector::openPageSpan()
{
  m_document->openPageSpan(librevenge::RVNGPropertyList());
}

void FB2ContentCollector::closePageSpan()
{
  m_document->closePageSpan();
}

void FB2ContentCollector::openBlock()
{
}

void FB2ContentCollector::closeBlock()
{
}

void FB2ContentCollector::openParagraph(const FB2BlockFormat &format)
{
  librevenge::RVNGPropertyList props(makePropertyList(format));
  if (0 < format.headingLevel)
  {
    std::string name("FictionBook2 Heading ");
    name += boost::lexical_cast<std::string, unsigned>(format.headingLevel);
    props.insert("style:display-name", name.c_str());
    props.insert("text:outline-level", format.headingLevel);
  }

  m_document->openParagraph(props);
  m_openPara = true;
}

void FB2ContentCollector::closeParagraph()
{
  m_document->closeParagraph();
  m_openPara = false;
}

void FB2ContentCollector::openSpan(const FB2Style &style)
{
  librevenge::RVNGPropertyList props(makePropertyList(style));

  optional<string> lang;
  optional<string> country;
  optional<string> script;

  if (!style.getTextFormat().lang.empty())
    parseLang(style.getTextFormat().lang, lang, country, script);
  else if (!style.getBlockFormat().lang.empty())
    parseLang(style.getBlockFormat().lang, lang, country, script);
  else if (m_metadata["dc:language"])
    parseLang(m_metadata["dc:language"]->getStr().cstr(), lang, country, script);

  if (bool(lang))
    props.insert("fo:language", get(lang).c_str());
  if (bool(country))
    props.insert("fo:country", get(country).c_str());
  if (bool(script))
    props.insert("fo:script", get(script).c_str());

  m_document->openSpan(props);
}

void FB2ContentCollector::closeSpan()
{
  m_document->closeSpan();
}

void FB2ContentCollector::insertText(const char *text)
{
  m_document->insertText(librevenge::RVNGString(text));
}

void FB2ContentCollector::openTable(const FB2BlockFormat &format)
{
  m_document->openTable(makePropertyList(format));
}

void FB2ContentCollector::closeTable()
{
  m_document->closeTable();
}

void FB2ContentCollector::openTableRow(const FB2BlockFormat &format)
{
  librevenge::RVNGPropertyList props;
  if (format.headerRow)
    props.insert("fo:is-header-row", true);

  m_document->openTableRow(props);
}

void FB2ContentCollector::closeTableRow()
{
  m_document->closeTableRow();
}

void FB2ContentCollector::openTableCell(int rowspan, int colspan)
{
  librevenge::RVNGPropertyList props;
  if (colspan > 0)
    props.insert("table:number-columns-spanned", colspan);
  if (rowspan > 0)
    props.insert("table:number-rows-spanned", rowspan);

  m_document->openTableCell(props);
}

void FB2ContentCollector::closeTableCell()
{
  m_document->closeTableCell();
}

void FB2ContentCollector::insertCoveredTableCell()
{
  m_document->insertCoveredTableCell(librevenge::RVNGPropertyList());
}

void FB2ContentCollector::insertFootnote(const char *id)
{
  const FB2Content *const note = m_notes.get(id);
  if (note)
  {
    librevenge::RVNGPropertyList props;
    props.insert("librevenge:number", m_currentFootnote);
    ++m_currentFootnote;

    m_document->openFootnote(props);
    note->unfold(*this);
    m_document->closeFootnote();
  }
}

void FB2ContentCollector::insertBitmap(const char *id)
{
  const FB2Content *const bitmap = m_bitmaps.get(id);
  if (bitmap)
  {
    librevenge::RVNGPropertyList props;

    if (m_openPara)
    {
      props.insert("style:horizontal-rel", "paragraph-content");
      props.insert("style:vertical-rel", "paragraph-content");
      props.insert("text:anchor-type", "character");
    }
    else
    {
      props.insert("style:horizontal-rel", "paragraph-start-margin");
      props.insert("style:vertical-rel", "paragraph-start-margin");
      props.insert("text:anchor-type", "paragraph");
    }
    props.insert("style:horizontal-pos", "left");
    props.insert("style:vertical-pos", "top");
    props.insert("style:wrap", "none");

    m_document->openFrame(props);
    bitmap->unfold(*this);
    m_document->closeFrame();
  }
}

void FB2ContentCollector::insertBitmapData(const char *contentType, const char *base64Data)
{
  librevenge::RVNGPropertyList props;
  props.insert("librevenge:mime-type", librevenge::RVNGString(contentType));

  const librevenge::RVNGBinaryData data(base64Data);
  props.insert("office:binary-data", data);

  m_document->insertBinaryObject(props);
}

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
