/***************************************************************************
    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 "bibtexexporter.h"
#include "bibtexhandler.h"
#include "../document.h"
#include "../collections/bibtexcollection.h"
#include "../latin1literal.h"
#include "../filehandler.h"
#include "../stringset.h"
#include "../tellico_debug.h"

#include <config.h>

#include <klocale.h>
#include <kdebug.h>
#include <kconfig.h>
#include <kcombobox.h>

#include <tqregexp.h>
#include <tqcheckbox.h>
#include <tqlayout.h>
#include <tqgroupbox.h>
#include <tqwhatsthis.h>
#include <tqlabel.h>
#include <tqhbox.h>

using Tellico::Export::BibtexExporter;

BibtexExporter::BibtexExporter() : Tellico::Export::Exporter(),
   m_expandMacros(false),
   m_packageURL(true),
   m_skipEmptyKeys(false),
   m_widget(0) {
}

TQString BibtexExporter::formatString() const {
  return i18n("Bibtex");
}

TQString BibtexExporter::fileFilter() const {
  return i18n("*.bib|Bibtex Files (*.bib)") + TQChar('\n') + i18n("*|All Files");
}

bool BibtexExporter::exec() {
  Data::CollPtr c = collection();
  if(!c || c->type() != Data::Collection::Bibtex) {
    return false;
  }
  const Data::BibtexCollection* coll = static_cast<const Data::BibtexCollection*>(c.data());

// there are some special attributes
// the entry-type specifies the entry type - book, inproceedings, whatever
  TQString typeField;
// the key specifies the cite-key
  TQString keyField;
// the crossref bibtex field can reference another entry
  TQString crossRefField;
  bool hasCrossRefs = false;

  const TQString bibtex = TQString::tqfromLatin1("bibtex");
// keep a list of all the 'ordinary' fields to iterate through later
  Data::FieldVec fields;
  Data::FieldVec vec = coll->fields();
  for(Data::FieldVec::Iterator it = vec.begin(); it != vec.end(); ++it) {
    TQString bibtexField = it->property(bibtex);
    if(bibtexField == Latin1Literal("entry-type")) {
      typeField = it->name();
    } else if(bibtexField == Latin1Literal("key")) {
      keyField = it->name();
    } else if(bibtexField == Latin1Literal("crossref")) {
      fields.append(it); // still output crossref field
      crossRefField = it->name();
      hasCrossRefs = true;
    } else if(!bibtexField.isEmpty()) {
      fields.append(it);
    }
  }

  if(typeField.isEmpty() || keyField.isEmpty()) {
    kdWarning() << "BibtexExporter::exec() - the collection must have fields defining "
                   "the entry-type and the key of the entry" << endl;
    return false;
  }
  if(fields.isEmpty()) {
    kdWarning() << "BibtexExporter::exec() - no bibtex field mapping exists in the collection." << endl;
    return false;
  }

  TQString text = TQString::tqfromLatin1("@comment{Generated by Tellico ")
               + TQString::tqfromLatin1(VERSION)
               + TQString::tqfromLatin1("}\n\n");

  if(!coll->preamble().isEmpty()) {
    text += TQString::tqfromLatin1("@preamble{") + coll->preamble() + TQString::tqfromLatin1("}\n\n");
  }

  const TQStringList macros = coll->macroList().keys();
  if(!m_expandMacros) {
    TQMap<TQString, TQString>::ConstIterator macroIt;
    for(macroIt = coll->macroList().constBegin(); macroIt != coll->macroList().constEnd(); ++macroIt) {
      if(!macroIt.data().isEmpty()) {
        text += TQString::tqfromLatin1("@string{")
                + macroIt.key()
                + TQString::tqfromLatin1("=")
                + BibtexHandler::exportText(macroIt.data(), macros)
                + TQString::tqfromLatin1("}\n\n");
      }
    }
  }

  // if anything is crossref'd, we have to do an initial scan through the
  // whole collection first
  StringSet crossRefKeys;
  if(hasCrossRefs) {
    for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
      crossRefKeys.add(entryIt->field(crossRefField));
    }
  }


  StringSet usedKeys;
  Data::ConstEntryVec crossRefs;
  TQString type, key, newKey, value;
  for(Data::EntryVec::ConstIterator entryIt = entries().begin(); entryIt != entries().end(); ++entryIt) {
    type = entryIt->field(typeField);
    if(type.isEmpty()) {
      kdWarning() << "BibtexExporter::text() - the entry for '" << entryIt->title()
                  << "' has no entry-type, skipping it!" << endl;
      continue;
    }

    key = entryIt->field(keyField);
    if(key.isEmpty()) {
      if(m_skipEmptyKeys) {
        continue;
      }
      key = BibtexHandler::bibtexKey(entryIt.data());
    } else {
      // check crossrefs, only counts for non-empty keys
      // if this entry is crossref'd, add it to the list, and skip it
      if(hasCrossRefs && crossRefKeys.has(key)) {
        crossRefs.append(entryIt.data());
        continue;
      }
    }

    newKey = key;
    char c = 'a';
    while(usedKeys.has(newKey)) {
      // duplicate found!
      newKey = key + c;
      ++c;
    }
    key = newKey;
    usedKeys.add(key);

    writeEntryText(text, fields, *entryIt, type, key);
  }

  // now write out crossrefs
  for(Data::ConstEntryVec::Iterator entryIt = crossRefs.begin(); entryIt != crossRefs.end(); ++entryIt) {
    // no need to check type

    key = entryIt->field(keyField);
    newKey = key;
    char c = 'a';
    while(usedKeys.has(newKey)) {
      // duplicate found!
      newKey = key + c;
      ++c;
    }
    key = newKey;
    usedKeys.add(key);

    writeEntryText(text, fields, *entryIt, entryIt->field(typeField), key);
  }

  return FileHandler::writeTextURL(url(), text, options() & ExportUTF8, options() & Export::ExportForce);
}

TQWidget* BibtexExporter::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("Bibtex Options"), m_widget);
  l->addWidget(box);

  m_checkExpandMacros = new TQCheckBox(i18n("Expand string macros"), box);
  m_checkExpandMacros->setChecked(m_expandMacros);
  TQWhatsThis::add(m_checkExpandMacros, i18n("If checked, the string macros will be expanded and no "
                                            "@string{} entries will be written."));

  m_checkPackageURL = new TQCheckBox(i18n("Use URL package"), box);
  m_checkPackageURL->setChecked(m_packageURL);
  TQWhatsThis::add(m_checkPackageURL, i18n("If checked, any URL fields will be wrapped in a "
                                          "\\url declaration."));

  m_checkSkipEmpty = new TQCheckBox(i18n("Skip entries with empty citation keys"), box);
  m_checkSkipEmpty->setChecked(m_skipEmptyKeys);
  TQWhatsThis::add(m_checkSkipEmpty, i18n("If checked, any entries without a bibtex citation key "
                                         "will be skipped."));

  TQHBox* hbox = new TQHBox(box);
  TQLabel* l1 = new TQLabel(i18n("Bibtex quotation style:") + ' ', hbox); // add a space for astheticss
  m_cbBibtexStyle = new KComboBox(hbox);
  m_cbBibtexStyle->insertItem(i18n("Braces"));
  m_cbBibtexStyle->insertItem(i18n("Quotes"));
  TQString whats = i18n("<qt>The quotation style used when exporting bibtex. All field values will "
                       " be escaped with either braces or quotation marks.</qt>");
  TQWhatsThis::add(l1, whats);
  TQWhatsThis::add(m_cbBibtexStyle, whats);
  if(BibtexHandler::s_quoteStyle == BibtexHandler::BRACES) {
    m_cbBibtexStyle->setCurrentItem(i18n("Braces"));
  } else {
    m_cbBibtexStyle->setCurrentItem(i18n("Quotes"));
  }

  l->addStretch(1);
  return m_widget;
}

void BibtexExporter::readOptions(KConfig* config_) {
  KConfigGroup group(config_, TQString::tqfromLatin1("ExportOptions - %1").tqarg(formatString()));
  m_expandMacros = group.readBoolEntry("Expand Macros", m_expandMacros);
  m_packageURL = group.readBoolEntry("URL Package", m_packageURL);
  m_skipEmptyKeys = group.readBoolEntry("Skip Empty Keys", m_skipEmptyKeys);

  if(group.readBoolEntry("Use Braces", true)) {
    BibtexHandler::s_quoteStyle = BibtexHandler::BRACES;
  } else {
    BibtexHandler::s_quoteStyle = BibtexHandler::TQUOTES;
  }
}

void BibtexExporter::saveOptions(KConfig* config_) {
  KConfigGroup group(config_, TQString::tqfromLatin1("ExportOptions - %1").tqarg(formatString()));
  m_expandMacros = m_checkExpandMacros->isChecked();
  group.writeEntry("Expand Macros", m_expandMacros);
  m_packageURL = m_checkPackageURL->isChecked();
  group.writeEntry("URL Package", m_packageURL);
  m_skipEmptyKeys = m_checkSkipEmpty->isChecked();
  group.writeEntry("Skip Empty Keys", m_skipEmptyKeys);

  bool useBraces = m_cbBibtexStyle->currentText() == i18n("Braces");
  group.writeEntry("Use Braces", useBraces);
  if(useBraces) {
    BibtexHandler::s_quoteStyle = BibtexHandler::BRACES;
  } else {
    BibtexHandler::s_quoteStyle = BibtexHandler::TQUOTES;
  }
}

void BibtexExporter::writeEntryText(TQString& text_, const Data::FieldVec& fields_, const Data::Entry& entry_,
                                    const TQString& type_, const TQString& key_) {
  const TQStringList macros = static_cast<const Data::BibtexCollection*>(Data::Document::self()->collection().data())->macroList().keys();
  const TQString bibtex = TQString::tqfromLatin1("bibtex");
  const TQString bibtexSep = TQString::tqfromLatin1("bibtex-separator");

  text_ += '@' + type_ + '{' + key_;

  TQString value;
  Data::FieldVec::ConstIterator fIt, end = fields_.constEnd();
  bool format = options() & Export::ExportFormatted;
  for(fIt = fields_.constBegin(); fIt != end; ++fIt) {
    value = entry_.field(fIt->name(), format);
    if(value.isEmpty()) {
      continue;
    }

    // If the entry is formatted as a name and allows multiple values
    // insert "and" in between them (e.g. author and editor)
    if(fIt->formatFlag() == Data::Field::FormatName
       && fIt->flags() & Data::Field::AllowMultiple) {
      value.replace(Data::Field::delimiter(), TQString::tqfromLatin1(" and "));
    } else if(fIt->flags() & Data::Field::AllowMultiple) {
      TQString bibsep = fIt->property(bibtexSep);
      if(!bibsep.isEmpty()) {
        value.replace(Data::Field::delimiter(), bibsep);
      }
    } else if(fIt->type() == Data::Field::Para) {
      // strip HTML from bibtex export
      TQRegExp stripHTML(TQString::tqfromLatin1("<.*>"), true);
      stripHTML.setMinimal(true);
      value.remove(stripHTML);
    } else if(fIt->property(bibtex) == Latin1Literal("pages")) {
      TQRegExp rx(TQString::tqfromLatin1("(\\d)-(\\d)"));
      for(int pos = rx.search(value); pos > -1; pos = rx.search(value, pos+2)) {
        value.replace(pos, 3, rx.cap(1)+"--"+rx.cap(2));
      }
    }

    if(m_packageURL && fIt->type() == Data::Field::URL) {
      bool b = BibtexHandler::s_quoteStyle == BibtexHandler::BRACES;
      value = (b ? TQChar('{') : TQChar('"'))
            + TQString::tqfromLatin1("\\url{") + BibtexHandler::exportText(value, macros) + TQChar('}')
            + (b ? TQChar('}') : TQChar('"'));
    } else if(fIt->type() != Data::Field::Number) {
      // numbers aren't escaped, nor will they have macros
      // if m_expandMacros is true, then macros is empty, so this is ok even then
      value = BibtexHandler::exportText(value, macros);
    }
    text_ += TQString::tqfromLatin1(",\n  ")
           + fIt->property(bibtex)
           + TQString::tqfromLatin1(" = ")
           + value;
  }
  text_ += TQString::tqfromLatin1("\n}\n\n");
}

#include "bibtexexporter.moc"
