/***************************************************************************
    copyright            : (C) 2003-2006 by Robby Stephenson
    email                : robby@periapsis.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of version 2 of the GNU General Public License as  *
 *   published by the Free Software Foundation;                            *
 *                                                                         *
 ***************************************************************************/

#include "tellicoxmlexporter.h"
#include "../collections/bibtexcollection.h"
#include "../imagefactory.h"
#include "../image.h"
#include "../controller.h" // needed for getting groupView pointer
#include "../entryitem.h"
#include "../latin1literal.h"
#include "../filehandler.h"
#include "../groupiterator.h"
#include "../tellico_utils.h"
#include "../tellico_kernel.h"
#include "../tellico_debug.h"
#include "tellico_xml.h"
#include "../document.h" // needed for sorting groups
#include "../translators/bibtexhandler.h" // needed for cleaning text

#include <klocale.h>
#include <kconfig.h>
#include <kmdcodec.h>
#include <kglobal.h>
#include <kcalendarsystem.h>

#include <tqlayout.h>
#include <tqgroupbox.h>
#include <tqcheckbox.h>
#include <tqwhatsthis.h>
#include <tqdom.h>
#include <tqtextcodec.h>

using Tellico::Export::TellicoXMLExporter;

TellicoXMLExporter::TellicoXMLExporter() : Exporter(),
      m_includeImages(false), m_includeGroups(false), m_widget(0) {
  setOptions(options() | Export::ExportImages | Export::ExportImageSize); // not included by default
}

TellicoXMLExporter::TellicoXMLExporter(Data::CollPtr coll) : Exporter(coll),
      m_includeImages(false), m_includeGroups(false), m_widget(0) {
  setOptions(options() | Export::ExportImages | Export::ExportImageSize); // not included by default
}

TQString TellicoXMLExporter::formatString() const {
  return i18n("XML");
}

TQString TellicoXMLExporter::fileFilter() const {
  return i18n("*.xml|XML Files (*.xml)") + TQChar('\n') + i18n("*|All Files");
}

bool TellicoXMLExporter::exec() {
  TQDomDocument doc = exportXML();
  if(doc.isNull()) {
    return false;
  }
  return FileHandler::writeTextURL(url(), doc.toString(),
                                   options() & ExportUTF8,
                                   options() & Export::ExportForce);
}

TQDomDocument TellicoXMLExporter::exportXML() const {
  // don't be hard on people with older versions. The only difference with DTD 10 was adding
  // a board game collection, so use 9 still unless it's a board game
  int exportVersion = (XML::syntaxVersion == 10 && collection()->type() != Data::Collection::BoardGame)
                    ? 9
                    : XML::syntaxVersion;

  TQDomImplementation impl;
  TQDomDocumentType doctype = impl.createDocumentType(TQString::tqfromLatin1("tellico"),
                                                     XML::pubTellico(exportVersion),
                                                     XML::dtdTellico(exportVersion));
  //default namespace
  const TQString& ns = XML::nsTellico;

  TQDomDocument dom = impl.createDocument(ns, TQString::tqfromLatin1("tellico"), doctype);

  // root tellico element
  TQDomElement root = dom.documentElement();

  TQString encodeStr = TQString::tqfromLatin1("version=\"1.0\" encoding=\"");
  if(options() & Export::ExportUTF8) {
    encodeStr += TQString::tqfromLatin1("UTF-8");
  } else {
    encodeStr += TQString::tqfromLatin1(TQTextCodec::codecForLocale()->mimeName());
  }
  encodeStr += TQChar('"');

  // createDocument creates a root node, insert the processing instruction before it
  dom.insertBefore(dom.createProcessingInstruction(TQString::tqfromLatin1("xml"), encodeStr), root);

  root.setAttribute(TQString::tqfromLatin1("syntaxVersion"), exportVersion);

  exportCollectionXML(dom, root, options() & Export::ExportFormatted);

  // clear image list
  m_images.clear();

  return dom;
}

TQString TellicoXMLExporter::exportXMLString() const {
  return exportXML().toString();
}

void TellicoXMLExporter::exportCollectionXML(TQDomDocument& dom_, TQDomElement& parent_, bool format_) const {
  Data::CollPtr coll = collection();
  if(!coll) {
    kdWarning() << "TellicoXMLExporter::exportCollectionXML() - no collection pointer!" << endl;
    return;
  }

  TQDomElement collElem = dom_.createElement(TQString::tqfromLatin1("collection"));
  collElem.setAttribute(TQString::tqfromLatin1("type"),       coll->type());
  collElem.setAttribute(TQString::tqfromLatin1("title"),      coll->title());

  TQDomElement fieldsElem = dom_.createElement(TQString::tqfromLatin1("fields"));
  collElem.appendChild(fieldsElem);

  Data::FieldVec fields = coll->fields();
  for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
    exportFieldXML(dom_, fieldsElem, fIt);
  }

  if(coll->type() == Data::Collection::Bibtex) {
    const Data::BibtexCollection* c = static_cast<const Data::BibtexCollection*>(coll.data());
    if(!c->preamble().isEmpty()) {
      TQDomElement preElem = dom_.createElement(TQString::tqfromLatin1("bibtex-preamble"));
      preElem.appendChild(dom_.createTextNode(c->preamble()));
      collElem.appendChild(preElem);
    }

    TQDomElement macrosElem = dom_.createElement(TQString::tqfromLatin1("macros"));
    for(StringMap::ConstIterator macroIt = c->macroList().constBegin(); macroIt != c->macroList().constEnd(); ++macroIt) {
      if(!macroIt.data().isEmpty()) {
        TQDomElement macroElem = dom_.createElement(TQString::tqfromLatin1("macro"));
        macroElem.setAttribute(TQString::tqfromLatin1("name"), macroIt.key());
        macroElem.appendChild(dom_.createTextNode(macroIt.data()));
        macrosElem.appendChild(macroElem);
      }
    }
    if(macrosElem.childNodes().count() > 0) {
      collElem.appendChild(macrosElem);
    }
  }

  Data::EntryVec evec = entries();
  for(Data::EntryVec::Iterator entry = evec.begin(); entry != evec.end(); ++entry) {
    exportEntryXML(dom_, collElem, entry, format_);
  }

  if(!m_images.isEmpty() && (options() & Export::ExportImages)) {
    TQDomElement imgsElem = dom_.createElement(TQString::tqfromLatin1("images"));
    collElem.appendChild(imgsElem);
    const TQStringList imageIds = m_images.toList();
    for(TQStringList::ConstIterator it = imageIds.begin(); it != imageIds.end(); ++it) {
      exportImageXML(dom_, imgsElem, *it);
    }
  }

  if(m_includeGroups) {
    exportGroupXML(dom_, collElem);
  }

  parent_.appendChild(collElem);

  // the borrowers and filters are in the tellico object, not the collection
  if(options() & Export::ExportComplete) {
    TQDomElement bElem = dom_.createElement(TQString::tqfromLatin1("borrowers"));
    Data::BorrowerVec borrowers = coll->borrowers();
    for(Data::BorrowerVec::Iterator bIt = borrowers.begin(); bIt != borrowers.end(); ++bIt) {
      exportBorrowerXML(dom_, bElem, bIt);
    }
    if(bElem.hasChildNodes()) {
      parent_.appendChild(bElem);
    }

    TQDomElement fElem = dom_.createElement(TQString::tqfromLatin1("filters"));
    FilterVec filters = coll->filters();
    for(FilterVec::Iterator fIt = filters.begin(); fIt != filters.end(); ++fIt) {
      exportFilterXML(dom_, fElem, fIt);
    }
    if(fElem.hasChildNodes()) {
      parent_.appendChild(fElem);
    }
  }
}

void TellicoXMLExporter::exportFieldXML(TQDomDocument& dom_, TQDomElement& parent_, Data::FieldPtr field_) const {
  TQDomElement elem = dom_.createElement(TQString::tqfromLatin1("field"));

  elem.setAttribute(TQString::tqfromLatin1("name"),     field_->name());
  elem.setAttribute(TQString::tqfromLatin1("title"),    field_->title());
  elem.setAttribute(TQString::tqfromLatin1("category"), field_->category());
  elem.setAttribute(TQString::tqfromLatin1("type"),     field_->type());
  elem.setAttribute(TQString::tqfromLatin1("flags"),    field_->flags());
  elem.setAttribute(TQString::tqfromLatin1("format"),   field_->formatFlag());

  if(field_->type() == Data::Field::Choice) {
    elem.setAttribute(TQString::tqfromLatin1("allowed"), field_->allowed().join(TQString::tqfromLatin1(";")));
  }

  // only save description if it's not equal to title, which is the default
  // title is never empty, so this indirectly checks for empty descriptions
  if(field_->description() != field_->title()) {
    elem.setAttribute(TQString::tqfromLatin1("description"), field_->description());
  }

  for(StringMap::ConstIterator it = field_->propertyList().begin(); it != field_->propertyList().end(); ++it) {
    if(it.data().isEmpty()) {
      continue;
    }
    TQDomElement e = dom_.createElement(TQString::tqfromLatin1("prop"));
    e.setAttribute(TQString::tqfromLatin1("name"), it.key());
    e.appendChild(dom_.createTextNode(it.data()));
    elem.appendChild(e);
  }

  parent_.appendChild(elem);
}

void TellicoXMLExporter::exportEntryXML(TQDomDocument& dom_, TQDomElement& parent_, Data::EntryPtr entry_, bool format_) const {
  TQDomElement entryElem = dom_.createElement(TQString::tqfromLatin1("entry"));
  entryElem.setAttribute(TQString::tqfromLatin1("id"), entry_->id());

  // iterate through every field for the entry
  Data::FieldVec fields = entry_->collection()->fields();
  for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
    TQString fieldName = fIt->name();

    // Date fields are special, don't format in export
    TQString fieldValue = (format_ && fIt->type() != Data::Field::Date) ? entry_->formattedField(fieldName)
                                                                       : entry_->field(fieldName);
    if(options() & ExportClean) {
      BibtexHandler::cleanText(fieldValue);
    }

    // if empty, then no field element is added and just continue
    if(fieldValue.isEmpty()) {
      continue;
    }

    // optionally, verify images exist
    if(fIt->type() == Data::Field::Image && (options() & Export::ExportVerifyImages)) {
      if(!ImageFactory::validImage(fieldValue)) {
        myDebug() << "TellicoXMLExporter::exportEntryXML() - entry: " << entry_->title() << endl;
        myDebug() << "TellicoXMLExporter::exportEntryXML() - skipping image: " << fieldValue << endl;
        continue;
      }
    }

    // if multiple versions are allowed, split them into separate elements
    if(fIt->flags() & Data::Field::AllowMultiple) {
      // parent element if field contains multiple values, child of entryElem
      // who cares about grammar, just add an 's' to the name
      TQDomElement parElem = dom_.createElement(fieldName + 's');
      entryElem.appendChild(parElem);

      // the space after the semi-colon is enforced when the field is set for the entry
      TQStringList fields = TQStringList::split(TQString::tqfromLatin1("; "), fieldValue, true);
      for(TQStringList::ConstIterator it = fields.begin(); it != fields.end(); ++it) {
        // element for field value, child of either entryElem or ParentElem
        TQDomElement fieldElem = dom_.createElement(fieldName);
        // special case for multi-column tables
        int ncols = 0;
        if(fIt->type() == Data::Field::Table) {
          bool ok;
          ncols = Tellico::toUInt(fIt->property(TQString::tqfromLatin1("columns")), &ok);
          if(!ok) {
            ncols = 1;
          }
        }
        if(ncols > 1) {
          for(int col = 0; col < ncols; ++col) {
            TQDomElement elem;
            elem = dom_.createElement(TQString::tqfromLatin1("column"));
            elem.appendChild(dom_.createTextNode((*it).section(TQString::tqfromLatin1("::"), col, col)));
            fieldElem.appendChild(elem);
          }
        } else {
          fieldElem.appendChild(dom_.createTextNode(*it));
        }
        parElem.appendChild(fieldElem);
      }
    } else {
      TQDomElement fieldElem = dom_.createElement(fieldName);
      entryElem.appendChild(fieldElem);
      // Date fields get special treatment
      if(fIt->type() == Data::Field::Date) {
        fieldElem.setAttribute(TQString::tqfromLatin1("calendar"), KGlobal::locale()->calendar()->calendarName());
        TQStringList s = TQStringList::split('-', fieldValue, true);
        if(s.count() > 0 && !s[0].isEmpty()) {
          TQDomElement e = dom_.createElement(TQString::tqfromLatin1("year"));
          fieldElem.appendChild(e);
          e.appendChild(dom_.createTextNode(s[0]));
        }
        if(s.count() > 1 && !s[1].isEmpty()) {
          TQDomElement e = dom_.createElement(TQString::tqfromLatin1("month"));
          fieldElem.appendChild(e);
          e.appendChild(dom_.createTextNode(s[1]));
        }
        if(s.count() > 2 && !s[2].isEmpty()) {
          TQDomElement e = dom_.createElement(TQString::tqfromLatin1("day"));
          fieldElem.appendChild(e);
          e.appendChild(dom_.createTextNode(s[2]));
        }
      } else if(fIt->type() == Data::Field::URL &&
                fIt->property(TQString::tqfromLatin1("relative")) == Latin1Literal("true") &&
                !url().isEmpty()) {
        // if a relative URL and url() is not empty, change the value!
        KURL old_url(Kernel::self()->URL(), fieldValue);
        fieldElem.appendChild(dom_.createTextNode(KURL::relativeURL(url(), old_url)));
      } else {
        fieldElem.appendChild(dom_.createTextNode(fieldValue));
      }
    }

    if(fIt->type() == Data::Field::Image) {
      // possible to have more than one entry with the same image
      // only want to include it in the output xml once
      m_images.add(fieldValue);
    }
  } // end field loop

  parent_.appendChild(entryElem);
}

void TellicoXMLExporter::exportImageXML(TQDomDocument& dom_, TQDomElement& parent_, const TQString& id_) const {
  if(id_.isEmpty()) {
    myDebug() << "TellicoXMLExporter::exportImageXML() - empty image!" << endl;
    return;
  }
//  myLog() << "TellicoXMLExporter::exportImageXML() - id = " << id_ << endl;

  TQDomElement imgElem = dom_.createElement(TQString::tqfromLatin1("image"));
  if(m_includeImages) {
    const Data::Image& img = ImageFactory::imageById(id_);
    if(img.isNull()) {
      myDebug() << "TellicoXMLExporter::exportImageXML() - null image - " << id_ << endl;
      return;
    }
    imgElem.setAttribute(TQString::tqfromLatin1("format"), img.format().data());
    imgElem.setAttribute(TQString::tqfromLatin1("id"),     img.id());
    imgElem.setAttribute(TQString::tqfromLatin1("width"),  img.width());
    imgElem.setAttribute(TQString::tqfromLatin1("height"), img.height());
    if(img.linkOnly()) {
      imgElem.setAttribute(TQString::tqfromLatin1("link"), TQString::tqfromLatin1("true"));
    }
    TQCString imgText = KCodecs::base64Encode(img.byteArray());
    imgElem.appendChild(dom_.createTextNode(TQString::tqfromLatin1(imgText)));
  } else {
    const Data::ImageInfo& info = ImageFactory::imageInfo(id_);
    if(info.isNull()) {
      return;
    }
    imgElem.setAttribute(TQString::tqfromLatin1("format"), info.format.data());
    imgElem.setAttribute(TQString::tqfromLatin1("id"),     info.id);
    // only load the images to read the size if necessary
    const bool loadImageIfNecessary = options() & Export::ExportImageSize;
    imgElem.setAttribute(TQString::tqfromLatin1("width"),  info.width(loadImageIfNecessary));
    imgElem.setAttribute(TQString::tqfromLatin1("height"), info.height(loadImageIfNecessary));
    if(info.linkOnly) {
      imgElem.setAttribute(TQString::tqfromLatin1("link"), TQString::tqfromLatin1("true"));
    }
  }
  parent_.appendChild(imgElem);
}

void TellicoXMLExporter::exportGroupXML(TQDomDocument& dom_, TQDomElement& parent_) const {
  Data::EntryVec vec = entries(); // need a copy for ::contains();
  bool exportAll = collection()->entries().count() == vec.count();
  // iterate over each group, which are the first tqchildren
  for(GroupIterator gIt = Controller::self()->groupIterator(); gIt.group(); ++gIt) {
    if(gIt.group()->isEmpty()) {
      continue;
    }
    TQDomElement groupElem = dom_.createElement(TQString::tqfromLatin1("group"));
    groupElem.setAttribute(TQString::tqfromLatin1("title"), gIt.group()->groupName());
    // now iterate over all entry items in the group
    Data::EntryVec sorted = Data::Document::self()->sortEntries(*gIt.group());
    for(Data::EntryVec::Iterator eIt = sorted.begin(); eIt != sorted.end(); ++eIt) {
      if(!exportAll && !vec.contains(eIt)) {
        continue;
      }
      TQDomElement entryRefElem = dom_.createElement(TQString::tqfromLatin1("entryRef"));
      entryRefElem.setAttribute(TQString::tqfromLatin1("id"), eIt->id());
      groupElem.appendChild(entryRefElem);
    }
    if(groupElem.hasChildNodes()) {
      parent_.appendChild(groupElem);
    }
  }
}

void TellicoXMLExporter::exportFilterXML(TQDomDocument& dom_, TQDomElement& parent_, FilterPtr filter_) const {
  TQDomElement filterElem = dom_.createElement(TQString::tqfromLatin1("filter"));
  filterElem.setAttribute(TQString::tqfromLatin1("name"), filter_->name());

  TQString match = (filter_->op() == Filter::MatchAll) ? TQString::tqfromLatin1("all") : TQString::tqfromLatin1("any");
  filterElem.setAttribute(TQString::tqfromLatin1("match"), match);

  for(TQPtrListIterator<FilterRule> it(*filter_); it.current(); ++it) {
    TQDomElement ruleElem = dom_.createElement(TQString::tqfromLatin1("rule"));
    ruleElem.setAttribute(TQString::tqfromLatin1("field"), it.current()->fieldName());
    ruleElem.setAttribute(TQString::tqfromLatin1("pattern"), it.current()->pattern());
    switch(it.current()->function()) {
      case FilterRule::FuncContains:
        ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("contains"));
        break;
      case FilterRule::FuncNotContains:
        ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("notcontains"));
        break;
      case FilterRule::FuncEquals:
        ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("equals"));
        break;
      case FilterRule::FuncNotEquals:
        ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("notequals"));
        break;
      case FilterRule::FuncRegExp:
        ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("regexp"));
        break;
      case FilterRule::FuncNotRegExp:
        ruleElem.setAttribute(TQString::tqfromLatin1("function"), TQString::tqfromLatin1("notregexp"));
        break;
      default:
        kdWarning() << "TellicoXMLExporter::exportFilterXML() - no matching rule function!" << endl;
    }
    filterElem.appendChild(ruleElem);
  }

  parent_.appendChild(filterElem);
}

void TellicoXMLExporter::exportBorrowerXML(TQDomDocument& dom_, TQDomElement& parent_,
                                           Data::BorrowerPtr borrower_) const {
  if(borrower_->isEmpty()) {
    return;
  }

  TQDomElement bElem = dom_.createElement(TQString::tqfromLatin1("borrower"));
  parent_.appendChild(bElem);

  bElem.setAttribute(TQString::tqfromLatin1("name"), borrower_->name());
  bElem.setAttribute(TQString::tqfromLatin1("uid"), borrower_->uid());

  const Data::LoanVec& loans = borrower_->loans();
  for(Data::LoanVec::ConstIterator it = loans.constBegin(); it != loans.constEnd(); ++it) {
    TQDomElement lElem = dom_.createElement(TQString::tqfromLatin1("loan"));
    bElem.appendChild(lElem);

    lElem.setAttribute(TQString::tqfromLatin1("uid"), it->uid());
    lElem.setAttribute(TQString::tqfromLatin1("entryRef"), it->entry()->id());
    lElem.setAttribute(TQString::tqfromLatin1("loanDate"), it->loanDate().toString(Qt::ISODate));
    lElem.setAttribute(TQString::tqfromLatin1("dueDate"), it->dueDate().toString(Qt::ISODate));
    if(it->inCalendar()) {
      lElem.setAttribute(TQString::tqfromLatin1("calendar"), TQString::tqfromLatin1("true"));
    }

    lElem.appendChild(dom_.createTextNode(it->note()));
  }
}

TQWidget* TellicoXMLExporter::widget(TQWidget* parent_, const char* name_/*=0*/) {
  if(m_widget && TQT_BASE_OBJECT(m_widget->parent()) == TQT_BASE_OBJECT(parent_)) {
    return m_widget;
  }

  m_widget = new TQWidget(parent_, name_);
  TQVBoxLayout* l = new TQVBoxLayout(m_widget);

  TQGroupBox* box = new TQGroupBox(1, Qt::Horizontal, i18n("Tellico XML Options"), m_widget);
  l->addWidget(box);

  m_checkIncludeImages = new TQCheckBox(i18n("Include images in XML document"), box);
  m_checkIncludeImages->setChecked(m_includeImages);
  TQWhatsThis::add(m_checkIncludeImages, i18n("If checked, the images in the document will be included "
                                             "in the XML stream as base64 encoded elements."));

  return m_widget;
}

void TellicoXMLExporter::readOptions(KConfig* config_) {
  KConfigGroup group(config_, TQString::tqfromLatin1("ExportOptions - %1").tqarg(formatString()));
  m_includeImages = group.readBoolEntry("Include Images", m_includeImages);
}

void TellicoXMLExporter::saveOptions(KConfig* config_) {
  m_includeImages = m_checkIncludeImages->isChecked();

  KConfigGroup group(config_, TQString::tqfromLatin1("ExportOptions - %1").tqarg(formatString()));
  group.writeEntry("Include Images", m_includeImages);
}

#include "tellicoxmlexporter.moc"
