/***************************************************************************
    copyright            : (C) 2001-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 "collection.h"
#include "field.h"
#include "entry.h"
#include "tellico_debug.h"
#include "latin1literal.h"
#include "tellico_utils.h"
#include "controller.h"
#include "collectionfactory.h"
#include "stringset.h"
#include "tellico_kernel.h"

#include <tdelocale.h>

#include <tqregexp.h>
#include <tqvaluestack.h>

using Tellico::Data::Collection;

const char* Collection::s_emptyGroupTitle = I18N_NOOP("(Empty)");
const TQString Collection::s_peopleGroupName = TQString::fromLatin1("_people");

Collection::Collection(const TQString& title_)
    : TQObject(), TDEShared(), m_nextEntryId(0), m_title(title_), m_entryIdDict(997)
    , m_trackGroups(false) {
  m_entryGroupDicts.setAutoDelete(true);

  m_id = getID();
}

Collection::~Collection() {
}

TQString Collection::typeName() const {
  return CollectionFactory::typeName(type());
}

bool Collection::addFields(FieldVec list_) {
  bool success = true;
  for(FieldVec::Iterator it = list_.begin(); it != list_.end(); ++it) {
    success &= addField(it);
  }
  return success;
}

bool Collection::addField(FieldPtr field_) {
  if(!field_) {
    return false;
  }

  // this essentially checks for duplicates
  if(hasField(field_->name())) {
    myDebug() << "Collection::addField() - replacing " << field_->name() << endl;
    removeField(fieldByName(field_->name()), true);
  }

  // it's not sufficient to merely check the new field
  if(dependentFieldHasRecursion(field_)) {
    field_->setDescription(TQString());
  }

  m_fields.append(field_);
  if(field_->formatFlag() == Field::FormatName) {
    m_peopleFields.append(field_); // list of people attributes
    if(m_peopleFields.count() > 1) {
      // the second time that a person field is added, add a "pseudo-group" for people
      if(m_entryGroupDicts.find(s_peopleGroupName) == 0) {
        EntryGroupDict* d = new EntryGroupDict();
        d->setAutoDelete(true);
        m_entryGroupDicts.insert(s_peopleGroupName, d);
        m_entryGroups.prepend(s_peopleGroupName);
      }
    }
  }
  m_fieldNameDict.insert(field_->name(), field_);
  m_fieldTitleDict.insert(field_->title(), field_);
  m_fieldNames << field_->name();
  m_fieldTitles << field_->title();
  if(field_->type() == Field::Image) {
    m_imageFields.append(field_);
  }

  if(!field_->category().isEmpty() && m_fieldCategories.findIndex(field_->category()) == -1) {
    m_fieldCategories << field_->category();
  }

  if(field_->flags() & Field::AllowGrouped) {
    // m_entryGroupsDicts autoDeletes each TQDict when the Collection d'tor is called
    EntryGroupDict* dict = new EntryGroupDict();
    dict->setAutoDelete(true);
    m_entryGroupDicts.insert(field_->name(), dict);
    // cache the possible groups of entries
    m_entryGroups << field_->name();
  }

  if(m_defaultGroupField.isEmpty() && field_->flags() & Field::AllowGrouped) {
    m_defaultGroupField = field_->name();
  }

  // refresh all dependent fields, in case one references this new one
  for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
    if(it->type() == Field::Dependent) {
      emit signalRefreshField(it);
    }
  }

  return true;
}

bool Collection::mergeField(FieldPtr newField_) {
  if(!newField_) {
    return false;
  }

  FieldPtr currField = fieldByName(newField_->name());
  if(!currField) {
    // does not exist in current collection, add it
    Data::FieldPtr f = new Field(*newField_);
    bool success = addField(f);
    Controller::self()->addedField(this, f);
    return success;
  }

  if(newField_->type() == Field::Table2) {
    newField_->setType(Data::Field::Table);
    newField_->setProperty(TQString::fromLatin1("columns"), TQChar('2'));
  }

  // the original field type is kept
  if(currField->type() != newField_->type()) {
    myDebug() << "Collection::mergeField() - skipping, field type mismatch for " << currField->title() << endl;
    return false;
  }

  // if field is a Choice, then make sure all values are there
  if(currField->type() == Field::Choice && currField->allowed() != newField_->allowed()) {
    TQStringList allowed = currField->allowed();
    const TQStringList& newAllowed = newField_->allowed();
    for(TQStringList::ConstIterator it = newAllowed.begin(); it != newAllowed.end(); ++it) {
      if(allowed.findIndex(*it) == -1) {
        allowed.append(*it);
      }
    }
    currField->setAllowed(allowed);
  }

  // don't change original format flags
  // don't change original category
  // add new description if current is empty
  if(currField->description().isEmpty()) {
    currField->setDescription(newField_->description());
    if(dependentFieldHasRecursion(currField)) {
      currField->setDescription(TQString());
    }
  }

  // if new field has additional extended properties, add those
  for(StringMap::ConstIterator it = newField_->propertyList().begin(); it != newField_->propertyList().end(); ++it) {
    const TQString propName = it.key();
    const TQString currValue = currField->property(propName);
    if(currValue.isEmpty()) {
      currField->setProperty(propName, it.data());
    } else if (it.data() != currValue) {
      if(currField->type() == Field::URL && propName == Latin1Literal("relative")) {
        kdWarning() << "Collection::mergeField() - relative URL property does not match for " << currField->name() << endl;
      } else if((currField->type() == Field::Table && propName == Latin1Literal("columns"))
             || (currField->type() == Field::Rating && propName == Latin1Literal("maximum"))) {
        bool ok;
        uint currNum = Tellico::toUInt(currValue, &ok);
        uint newNum = Tellico::toUInt(it.data(), &ok);
        if(newNum > currNum) { // bigger values
          currField->setProperty(propName, TQString::number(newNum));
        }
      } else if(currField->type() == Field::Rating && propName == Latin1Literal("minimum")) {
        bool ok;
        uint currNum = Tellico::toUInt(currValue, &ok);
        uint newNum = Tellico::toUInt(it.data(), &ok);
        if(newNum < currNum) { // smaller values
          currField->setProperty(propName, TQString::number(newNum));
        }
      }
    }
  }

  // combine flags
  currField->setFlags(currField->flags() | newField_->flags());
  return true;
}

// be really careful with these field pointers, try not to call too many other functions
// which may depend on the field list
bool Collection::modifyField(FieldPtr newField_) {
  if(!newField_) {
    return false;
  }
//  myDebug() << "Collection::modifyField() - " << newField_->name() << endl;

// the field name never changes
  const TQString fieldName = newField_->name();
  FieldPtr oldField = fieldByName(fieldName);
  if(!oldField) {
    myDebug() << "Collection::modifyField() - no field named " << fieldName << endl;
    return false;
  }

  // update name dict
  m_fieldNameDict.replace(fieldName, newField_);

  // update titles
  const TQString oldTitle = oldField->title();
  const TQString newTitle = newField_->title();
  if(oldTitle == newTitle) {
    m_fieldTitleDict.replace(newTitle, newField_);
  } else {
    m_fieldTitleDict.remove(oldTitle);
    m_fieldTitles.remove(oldTitle);
    m_fieldTitleDict.insert(newTitle, newField_);
    m_fieldTitles.append(newTitle);
  }

  // now replace the field pointer in the list
  FieldVec::Iterator it = m_fields.find(oldField);
  if(it != m_fields.end()) {
    m_fields.insert(it, newField_);
    m_fields.remove(oldField);
  } else {
    myDebug() << "Collection::modifyField() - no index found!" << endl;
    return false;
  }

  // update category list.
  if(oldField->category() != newField_->category()) {
    m_fieldCategories.clear();
    for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
      // add category if it's not in the list yet
      if(!it->category().isEmpty() && !m_fieldCategories.contains(it->category())) {
        m_fieldCategories += it->category();
      }
    }
  }

  if(dependentFieldHasRecursion(newField_)) {
    newField_->setDescription(TQString());
  }

  // keep track of if the entry groups will need to be reset
  bool resetGroups = false;

  // if format is different, go ahead and invalidate all formatted entry values
  if(oldField->formatFlag() != newField_->formatFlag()) {
    // invalidate cached format strings of all entry attributes of this name
    for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) {
      it->invalidateFormattedFieldValue(fieldName);
    }
    resetGroups = true;
  }

  // check to see if the people "pseudo-group" needs to be updated
  // only if only one of the two is a name
  bool wasPeople = oldField->formatFlag() == Field::FormatName;
  bool isPeople = newField_->formatFlag() == Field::FormatName;
  if(wasPeople) {
    m_peopleFields.remove(oldField);
    if(!isPeople) {
      resetGroups = true;
    }
  }
  if(isPeople) {
    // if there's more than one people field and no people dict exists yet, add it
    if(m_peopleFields.count() > 1 && m_entryGroupDicts.find(s_peopleGroupName) == 0) {
      EntryGroupDict* d = new EntryGroupDict();
      d->setAutoDelete(true);
      m_entryGroupDicts.insert(s_peopleGroupName, d);
      // put it at the top of the list
      m_entryGroups.prepend(s_peopleGroupName);
    }
    m_peopleFields.append(newField_);
    if(!wasPeople) {
      resetGroups = true;
    }
  }

  bool wasGrouped = oldField->flags() & Field::AllowGrouped;
  bool isGrouped = newField_->flags() & Field::AllowGrouped;
  if(wasGrouped) {
    if(!isGrouped) {
      // in order to keep list in the same order, don't remove unless new field is not groupable
      m_entryGroups.remove(fieldName);
      m_entryGroupDicts.remove(fieldName);
      myDebug() << "Collection::modifyField() - no longer grouped: " << fieldName << endl;
      resetGroups = true;
    } else {
      // don't do this, it wipes out the old groups!
//      m_entryGroupDicts.replace(fieldName, new EntryGroupDict());
    }
  } else if(isGrouped) {
    EntryGroupDict* d = new EntryGroupDict();
    d->setAutoDelete(true);
    m_entryGroupDicts.insert(fieldName, d);
    if(!wasGrouped) {
      // cache the possible groups of entries
      m_entryGroups << fieldName;
    }
    resetGroups = true;
  }

  if(oldField->type() == Field::Image) {
    m_imageFields.remove(oldField);
  }
  if(newField_->type() == Field::Image) {
    m_imageFields.append(newField_);
  }

  if(resetGroups) {
    myLog() << "Collection::modifyField() - invalidating groups" << endl;
    invalidateGroups();
  }

  // now to update all entries if the field is a dependent and the description changed
  if(newField_->type() == Field::Dependent && oldField->description() != newField_->description()) {
    emit signalRefreshField(newField_);
  }

  return true;
}

bool Collection::removeField(const TQString& name_, bool force_) {
  return removeField(fieldByName(name_), force_);
}

// force allows me to force the deleting of the title field if I need to
bool Collection::removeField(FieldPtr field_, bool force_/*=false*/) {
  if(!field_ || !m_fields.contains(field_)) {
    if(field_) {
      myDebug() << "Collection::removeField - false: " << field_->name() << endl;
    }
    return false;
  }
//  myDebug() << "Collection::removeField() - name = " << field_->name() << endl;

  // can't delete the title field
  if((field_->flags() & Field::NoDelete) && !force_) {
    return false;
  }

  bool success = true;
  if(field_->formatFlag() == Field::FormatName) {
    success &= m_peopleFields.remove(field_);
  }

  if(field_->type() == Field::Image) {
    success &= m_imageFields.remove(field_);
  }
  success &= m_fieldNameDict.remove(field_->name());
  success &= m_fieldTitleDict.remove(field_->title());
  success &= m_fieldNames.remove(field_->name());
  success &= m_fieldTitles.remove(field_->title());

  if(fieldsByCategory(field_->category()).count() == 1) {
    success &= m_fieldCategories.remove(field_->category());
  }

  for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) {
    // setting the fields to an empty string removes the value from the entry's list
    it->setField(field_, TQString());
  }

  if(field_->flags() & Field::AllowGrouped) {
    success &= m_entryGroupDicts.remove(field_->name());
    success &= m_entryGroups.remove(field_->name());
    if(field_->name() == m_defaultGroupField) {
      setDefaultGroupField(m_entryGroups[0]);
    }
  }

  success &= m_fields.remove(field_);

  // refresh all dependent fields, rather lazy, but there's
  // likely to be weird effects when checking dependent fields
  // while removing one, so refresh all of them
  for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
    if(it->type() == Field::Dependent) {
      emit signalRefreshField(it);
    }
  }

  return success;
}

void Collection::reorderFields(const FieldVec& list_) {
// assume the lists have the same pointers!
  m_fields = list_;

  // also reset category list, since the order may have changed
  m_fieldCategories.clear();
  for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
    if(!it->category().isEmpty() && !m_fieldCategories.contains(it->category())) {
      m_fieldCategories << it->category();
    }
  }
}

void Collection::addEntries(EntryVec entries_) {
  if(entries_.isEmpty()) {
    return;
  }

  for(EntryVec::Iterator entry = entries_.begin(); entry != entries_.end(); ++entry) {
    bool foster = false;
    if(this != entry->collection()) {
      entry->setCollection(this);
      foster = true;
    }

    m_entries.append(entry);
//  myDebug() << "Collection::addEntries() - added entry (" << entry->title() << ")" << endl;

    if(entry->id() >= m_nextEntryId) {
      m_nextEntryId = entry->id() + 1;
    } else if(entry->id() == -1) {
      entry->setId(m_nextEntryId);
      ++m_nextEntryId;
    } else if(m_entryIdDict.find(entry->id())) {
      if(!foster) {
        myDebug() << "Collection::addEntries() - the collection already has an entry with id = " << entry->id() << endl;
      }
      entry->setId(m_nextEntryId);
      ++m_nextEntryId;
    }
    m_entryIdDict.insert(entry->id(), entry.data());
  }
  if(m_trackGroups) {
    populateCurrentDicts(entries_);
  }
}

void Collection::removeEntriesFromDicts(EntryVec entries_) {
  PtrVector<EntryGroup> modifiedGroups;
  for(EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
    // need a copy of the vector since it gets changed
    PtrVector<EntryGroup> groups = entry->groups();
    for(PtrVector<EntryGroup>::Iterator group = groups.begin(); group != groups.end(); ++group) {
      if(entry->removeFromGroup(group.ptr()) && !modifiedGroups.contains(group.ptr())) {
        modifiedGroups.push_back(group.ptr());
      }
      if(group->isEmpty() && !m_groupsToDelete.contains(group.ptr())) {
        m_groupsToDelete.push_back(group.ptr());
      }
    }
  }
  emit signalGroupsModified(this, modifiedGroups);
}

// this function gets called whenever an entry is modified. Its purpose is to keep the
// groupDicts current. It first removes the entry from every group to which it belongs,
// then it repopulates the dicts with the entry's fields
void Collection::updateDicts(EntryVec entries_) {
  if(entries_.isEmpty()) {
    return;
  }

  removeEntriesFromDicts(entries_);
  populateCurrentDicts(entries_);
  cleanGroups();
}

bool Collection::removeEntries(EntryVec vec_) {
  if(vec_.isEmpty()) {
    return false;
  }

//  myDebug() << "Collection::deleteEntry() - deleted entry - " << entry_->title() << endl;
  removeEntriesFromDicts(vec_);
  bool success = true;
  for(EntryVecIt entry = vec_.begin(); entry != vec_.end(); ++entry) {
    m_entryIdDict.remove(entry->id());
    success &= m_entries.remove(entry);
  }
  cleanGroups();
  return success;
}

Tellico::Data::FieldVec Collection::fieldsByCategory(const TQString& cat_) {
#ifndef NDEBUG
  if(m_fieldCategories.findIndex(cat_) == -1) {
    myDebug() << "Collection::fieldsByCategory() - '" << cat_ << "' is not in category list" << endl;
  }
#endif
  if(cat_.isEmpty()) {
    myDebug() << "Collection::fieldsByCategory() - empty category!" << endl;
    return FieldVec();
  }

  FieldVec list;
  for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) {
    if(it->category() == cat_) {
      list.append(it);
    }
  }
  return list;
}

const TQString& Collection::fieldNameByTitle(const TQString& title_) const {
  if(title_.isEmpty()) {
    return TQString();
  }
  FieldPtr f = fieldByTitle(title_);
  if(!f) { // might happen in MainWindow::saveCollectionOptions
    return TQString();
  }
  return f->name();
}

const TQString& Collection::fieldTitleByName(const TQString& name_) const {
  if(name_.isEmpty()) {
    return TQString();
  }
  FieldPtr f = fieldByName(name_);
  if(!f) {
    kdWarning() << "Collection::fieldTitleByName() - no field named " << name_ << endl;
    return TQString();
  }
  return f->title();
}

TQStringList Collection::valuesByFieldName(const TQString& name_) const {
  if(name_.isEmpty()) {
    return TQStringList();
  }
  bool multiple = (fieldByName(name_)->flags() & Field::AllowMultiple);

  StringSet values;
  for(EntryVec::ConstIterator it = m_entries.begin(); it != m_entries.end(); ++it) {
    if(multiple) {
      values.add(it->fields(name_, false));
    } else {
      values.add(it->field(name_));
    }
  } // end entry loop

  return values.toList();
}

Tellico::Data::FieldPtr Collection::fieldByName(const TQString& name_) const {
  return m_fieldNameDict.isEmpty() ? 0 : name_.isEmpty() ? 0 : m_fieldNameDict.find(name_);
}

Tellico::Data::FieldPtr Collection::fieldByTitle(const TQString& title_) const {
  return m_fieldTitleDict.isEmpty() ? 0 : title_.isEmpty() ? 0 : m_fieldTitleDict.find(title_);
}

bool Collection::hasField(const TQString& name_) const {
  return fieldByName(name_) != 0;
}

bool Collection::isAllowed(const TQString& key_, const TQString& value_) const {
  // empty string is always allowed
  if(value_.isEmpty()) {
    return true;
  }

  // find the field with a name of 'key_'
  FieldPtr field = fieldByName(key_);

  // if the type is not multiple choice or if value_ is allowed, return true
  if(field && (field->type() != Field::Choice || field->allowed().findIndex(value_) > -1)) {
    return true;
  }

  return false;
}

Tellico::Data::EntryGroupDict* Collection::entryGroupDictByName(const TQString& name_) {
//  myDebug() << "Collection::entryGroupDictByName() - " << name_ << endl;
  if(name_.isEmpty()) {
    return 0;
  }
  EntryGroupDict* dict = m_entryGroupDicts.isEmpty() ? 0 : m_entryGroupDicts.find(name_);
  if(dict && dict->isEmpty()) {
    GUI::CursorSaver cs;
    const bool b = signalsBlocked();
    // block signals so all the group created/modified signals don't fire
    blockSignals(true);
    populateDict(dict, name_, m_entries);
    blockSignals(b);
  }
  return dict;
}

void Collection::populateDict(EntryGroupDict* dict_, const TQString& fieldName_, EntryVec entries_) {
//  myDebug() << "Collection::populateDict() - " << fieldName_ << endl;
  bool isBool = hasField(fieldName_) && fieldByName(fieldName_)->type() == Field::Bool;

  PtrVector<EntryGroup> modifiedGroups;
  for(EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) {
    TQStringList groups = entryGroupNamesByField(entry, fieldName_);
    for(TQStringList::ConstIterator groupIt = groups.begin(); groupIt != groups.end(); ++groupIt) {
      // find the group for this group name
      // bool fields used the field title
      TQString groupTitle = *groupIt;
      if(isBool && groupTitle != i18n(s_emptyGroupTitle)) {
        groupTitle = fieldTitleByName(fieldName_);
      }
      EntryGroup* group = dict_->find(groupTitle);
      // if the group doesn't exist, create it
      if(!group) {
        group = new EntryGroup(groupTitle, fieldName_);
        dict_->insert(groupTitle, group);
      } else if(group->isEmpty()) {
        // if it's empty, then it was added to the vector of groups to delete
        // remove it from that vector now that we're adding to it
        m_groupsToDelete.remove(group);
      }
      if(entry->addToGroup(group)) {
        modifiedGroups.push_back(group);
      }
    } // end group loop
  } // end entry loop
  emit signalGroupsModified(this, modifiedGroups);
}

void Collection::populateCurrentDicts(EntryVec entries_) {
  if(m_entryGroupDicts.isEmpty()) {
    return;
  }

  // special case when adding an entry to a new empty collection
  // there are no existing non-empty groups
  bool allEmpty = true;

  // iterate over all the possible groupDicts
  // for each dict, get the value of that field for the entry
  // if multiple values are allowed, split the value and then insert the
  // entry pointer into the dict for each value
  TQDictIterator<EntryGroupDict> dictIt(m_entryGroupDicts);
  for( ; dictIt.current(); ++dictIt) {
    // only populate if it's not empty, since they are
    // populated on demand
    if(!dictIt.current()->isEmpty()) {
      populateDict(dictIt.current(), dictIt.currentKey(), entries_);
      allEmpty = false;
    }
  }

  if(allEmpty) {
    // need to populate the current group dict
    const TQString group = Controller::self()->groupBy();
    EntryGroupDict* dict = m_entryGroupDicts[group];
    if(dict) {
      populateDict(dict, group, entries_);
    }
  }
}

// return a string list for all the groups that the entry belongs to
// for a given field. Normally, this would just be splitting the entry's value
// for the field, but if the field name is the people pseudo-group, then it gets
// a bit more complicated
TQStringList Collection::entryGroupNamesByField(EntryPtr entry_, const TQString& fieldName_) {
  if(fieldName_ != s_peopleGroupName) {
    return entry_->groupNamesByFieldName(fieldName_);
  }

  StringSet values;
  for(FieldVec::Iterator it = m_peopleFields.begin(); it != m_peopleFields.end(); ++it) {
    values.add(entry_->groupNamesByFieldName(it->name()));
  }
  values.remove(i18n(s_emptyGroupTitle));
  return values.toList();
}

void Collection::invalidateGroups() {
  TQDictIterator<EntryGroupDict> dictIt(m_entryGroupDicts);
  for( ; dictIt.current(); ++dictIt) {
    dictIt.current()->clear();
  }

  // populateDicts() will make signals that the group view is connected to, block those
  blockSignals(true);
  for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) {
    it->invalidateFormattedFieldValue();
    it->clearGroups();
  }
  blockSignals(false);
}

Tellico::Data::EntryPtr Collection::entryById(long id_) {
  return m_entryIdDict[id_];
}

void Collection::addBorrower(Data::BorrowerPtr borrower_) {
  if(!borrower_) {
    return;
  }
  m_borrowers.append(borrower_);
}

void Collection::addFilter(FilterPtr filter_) {
  if(!filter_) {
    return;
  }

  m_filters.append(filter_);
}

bool Collection::removeFilter(FilterPtr filter_) {
  if(!filter_) {
    return false;
  }

  // TODO: test for success
  m_filters.remove(filter_);
  return true;
}

void Collection::clear() {
  // since the collection holds a pointer to each entry and each entry
  // hold a pointer to the collection, and they're both sharedptrs,
  // neither will ever get deleted, unless the collection removes
  // all held pointers, specifically to entries
  m_fields.clear();
  m_peopleFields.clear();
  m_imageFields.clear();
  m_fieldNameDict.clear();
  m_fieldTitleDict.clear();

  m_entries.clear();
  m_entryIdDict.clear();
  m_entryGroupDicts.clear();
  m_groupsToDelete.clear();
  m_filters.clear();
  m_borrowers.clear();
}

void Collection::cleanGroups() {
  for(PtrVector<EntryGroup>::Iterator it = m_groupsToDelete.begin(); it != m_groupsToDelete.end(); ++it) {
    EntryGroupDict* dict = entryGroupDictByName(it->fieldName());
    if(!dict) {
      continue;
    }
    dict->remove(it->groupName());
  }
  m_groupsToDelete.clear();
}

bool Collection::dependentFieldHasRecursion(FieldPtr field_) {
  if(!field_ || field_->type() != Field::Dependent) {
    return false;
  }

  StringSet fieldNamesFound;
  fieldNamesFound.add(field_->name());
  TQValueStack<FieldPtr> fieldsToCheck;
  fieldsToCheck.push(field_);
  while(!fieldsToCheck.isEmpty()) {
    FieldPtr f = fieldsToCheck.pop();
    const TQStringList depFields = f->dependsOn();
    for(TQStringList::ConstIterator it = depFields.begin(); it != depFields.end(); ++it) {
      if(fieldNamesFound.has(*it)) {
        // we have recursion
        return true;
      }
      fieldNamesFound.add(*it);
      FieldPtr f = fieldByName(*it);
      if(!f) {
        f = fieldByTitle(*it);
      }
      if(f) {
        fieldsToCheck.push(f);
      }
    }
  }
  return false;
}

int Collection::sameEntry(Data::EntryPtr entry1_, Data::EntryPtr entry2_) const {
  if(!entry1_ || !entry2_) {
    return 0;
  }
  // used to just return 0, but we really want a default generic implementation
  // that specific collections can override.

  // start with twice the title score
  // and since the minimum is > 10, then need more than just a perfect title match
  int res = 2*Entry::compareValues(entry1_, entry2_, TQString::fromLatin1("title"), this);
  // then add score for each field
  FieldVec fields = entry1_->collection()->fields();
  for(Data::FieldVecIt it = fields.begin(); it != fields.end(); ++it) {
    res += Entry::compareValues(entry1_, entry2_, it->name(), this);
  }
  return res;
}

// static
// merges values from e2 into e1
bool Collection::mergeEntry(EntryPtr e1, EntryPtr e2, bool overwrite_, bool askUser_) {
  if(!e1 || !e2) {
    myDebug() << "Collection::mergeEntry() - bad entry pointer" << endl;
    return false;
  }
  bool ret = true;
  FieldVec fields = e1->collection()->fields();
  for(FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) {
    if(e2->field(field).isEmpty()) {
      continue;
    }
//    myLog() << "Collection::mergeEntry() - reading field: " << field->name() << endl;
    if(overwrite_ || e1->field(field).isEmpty()) {
//      myLog() << e1->title() << ": updating field(" << field->name() << ") to " << e2->field(field->name()) << endl;
      e1->setField(field, e2->field(field));
      ret = true;
    } else if(e1->field(field) == e2->field(field)) {
      continue;
    } else if(field->type() == Field::Para) {
      // for paragraph fields, concatenate the values, if they're not equal
      e1->setField(field, e1->field(field) + TQString::fromLatin1("<br/><br/>") + e2->field(field));
      ret = true;
    } else if(field->type() == Field::Table) {
      // if field F is a table-type field (album tracks, files, etc.), merge rows (keep their position)
      // if e1's F val in [row i, column j] empty, replace with e2's val at same position
      // if different (non-empty) vals at same position, CONFLICT!
      const TQString sep = TQString::fromLatin1("::");
      TQStringList vals1 = e1->fields(field, false);
      TQStringList vals2 = e2->fields(field, false);
      while(vals1.count() < vals2.count()) {
        vals1 += TQString();
      }
      for(uint i = 0; i < vals2.count(); ++i) {
        if(vals2[i].isEmpty()) {
          continue;
        }
        if(vals1[i].isEmpty()) {
          vals1[i] = vals2[i];
          ret = true;
        } else {
          TQStringList parts1 = TQStringList::split(sep, vals1[i], true);
          TQStringList parts2 = TQStringList::split(sep, vals2[i], true);
          bool changedPart = false;
          while(parts1.count() < parts2.count()) {
            parts1 += TQString();
          }
          for(uint j = 0; j < parts2.count(); ++j) {
            if(parts2[j].isEmpty()) {
              continue;
            }
            if(parts1[j].isEmpty()) {
              parts1[j] = parts2[j];
              changedPart = true;
            } else if(askUser_ && parts1[j] != parts2[j]) {
              int ret = Kernel::self()->askAndMerge(e1, e2, field, parts1[j], parts2[j]);
              if(ret == 0) {
                return false; // we got cancelled
              }
              if(ret == 1) {
                parts1[j] = parts2[j];
                changedPart = true;
              }
            }
          }
          if(changedPart) {
            vals1[i] = parts1.join(sep);
            ret = true;
          }
        }
      }
      if(ret) {
        e1->setField(field, vals1.join(TQString::fromLatin1("; ")));
      }
// remove the merging due to use comments
// maybe in the future have a more intelligent way
#if 0
    } else if(field->flags() & Data::Field::AllowMultiple) {
      // if field F allows multiple values and not a Table (see above case),
      // e1's F values = (e1's F values) U (e2's F values) (union)
      // replace e1's field with union of e1's and e2's values for this field
      TQStringList items1 = e1->fields(field, false);
      TQStringList items2 = e2->fields(field, false);
      for(TQStringList::ConstIterator it = items2.begin(); it != items2.end(); ++it) {
        // possible to have one value formatted and the other one not...
        if(!items1.contains(*it) && !items1.contains(Field::format(*it, field->formatFlag()))) {
          items1.append(*it);
        }
      }
// not sure if I think it should be sorted or not
//      items1.sort();
      e1->setField(field, items1.join(TQString::fromLatin1("; ")));
      ret = true;
#endif
    } else if(askUser_ && e1->field(field) != e2->field(field)) {
      int ret = Kernel::self()->askAndMerge(e1, e2, field);
      if(ret == 0) {
        return false; // we got cancelled
      }
      if(ret == 1) {
        e1->setField(field, e2->field(field));
      }
    }
  }
  return ret;
}

long Collection::getID() {
  static long id = 0;
  return ++id;
}

#include "collection.moc"
