/***************************************************************************
    copyright            : (C) 2001-2007 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 "groupview.h"
#include "collection.h"
#include "document.h"
#include "field.h"
#include "filter.h"
#include "controller.h"
#include "entryitem.h"
#include "entrygroupitem.h"
#include "entry.h"
#include "field.h"
#include "filter.h"
#include "tellico_kernel.h"
#include "listviewcomparison.h"
#include "../tellico_debug.h"

#include <kpopupmenu.h>
#include <klocale.h>
#include <kiconloader.h>
#include <kaction.h>

#include <tqstringlist.h>
#include <tqcolor.h>
#include <tqregexp.h>
#include <tqheader.h>

using Tellico::GroupView;

GroupView::GroupView(TQWidget* parent_, const char* name_/*=0*/)
    : GUI::ListView(parent_, name_), m_notSortedYet(true), m_coll(0) {
  addColumn(TQString()); // header text gets updated later
  header()->setStretchEnabled(true, 0);
  setResizeMode(TQListView::NoColumn);
  setRootIsDecorated(true);
  setShowSortIndicator(true);
  setTreeStepSize(15);
  setFullWidth(true);

  connect(this, TQT_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)),
          TQT_SLOT(contextMenuRequested(TQListViewItem*, const TQPoint&, int)));

  connect(this, TQT_SIGNAL(expanded(TQListViewItem*)),
          TQT_SLOT(slotExpanded(TQListViewItem*)));

  connect(this, TQT_SIGNAL(collapsed(TQListViewItem*)),
          TQT_SLOT(slotCollapsed(TQListViewItem*)));

  m_groupOpenPixmap = SmallIcon(TQString::fromLatin1("folder_open"));
  m_groupClosedPixmap = SmallIcon(TQString::fromLatin1("folder"));
}

Tellico::EntryGroupItem* GroupView::addGroup(Data::EntryGroup* group_) {
  if(group_->isEmpty()) {
    return 0;
  }
  int type = -1;
  if(m_coll && m_coll->hasField(group_->fieldName())) {
    type = m_coll->fieldByName(group_->fieldName())->type();
  }
  EntryGroupItem* item = new EntryGroupItem(this, group_, type);
  if(group_->groupName() == i18n(Data::Collection::s_emptyGroupTitle)) {
    item->setPixmap(0, SmallIcon(TQString::fromLatin1("folder_red")));
    item->setSortWeight(10);
  } else {
    item->setPixmap(0, m_groupClosedPixmap);
  }

  m_groupDict.insert(group_->groupName(), item);
  item->setExpandable(!group_->isEmpty());

  return item;
}

void GroupView::slotReset() {
  clear();
  m_groupDict.clear();
}

void GroupView::removeCollection(Data::CollPtr coll_) {
  if(!coll_) {
    kdWarning() << "GroupView::removeCollection() - null coll pointer!" << endl;
    return;
  }

//  myDebug() << "GroupView::removeCollection() - " << coll_->title() << endl;

  blockSignals(true);
  slotReset();
  blockSignals(false);
}

void GroupView::slotModifyGroups(Data::CollPtr coll_, PtrVector<Data::EntryGroup> groups_) {
  if(!coll_ || groups_.isEmpty()) {
    kdWarning() << "GroupView::slotModifyGroups() - null coll or group pointer!" << endl;
    return;
  }

  for(PtrVector<Data::EntryGroup>::Iterator group = groups_.begin(); group != groups_.end(); ++group) {
    // if the entries aren't grouped by field of the modified group,
    // we don't care, so return
    if(m_groupBy != group->fieldName()) {
      continue;
    }

//    myDebug() << "GroupView::slotModifyGroups() - " << group->fieldName() << "/" << group->groupName() << endl;
    EntryGroupItem* par = m_groupDict.find(group->groupName());
    if(par) {
      if(group->isEmpty()) {
        m_groupDict.remove(par->text(0));
        delete par;
        continue;
      }
      // the group might get deleted and recreated out from under us,
      // so do a sanity check
      par->setGroup(group.ptr());
    } else {
      if(group->isEmpty()) {
        myDebug() << "GroupView::slotModifyGroups() - trying to add empty group" << endl;
        continue;
      }
      par = addGroup(group.ptr());
    }

    setUpdatesEnabled(false);
    bool open = par->isOpen();
    par->setOpen(false); // closing and opening the item will clear the items
    par->setOpen(open);
    setUpdatesEnabled(true);
  }
  // don't want any selected
  clearSelection();
  sort(); // in case the count changed, or group name
}

// don't 'shadow' TQListView::setSelected
void GroupView::setEntrySelected(Data::EntryPtr entry_) {
//  myDebug() << "GroupView::slotSetSelected()" << endl;
  // if entry_ is null pointer, set no selected
  if(!entry_) {
    // don't move this one outside the block since it calls setCurrentItem(0)
    clearSelection();
    return;
  }

  // if the selected entry is the same as the current one, just return
  GUI::ListViewItem* it = static_cast<GUI::ListViewItem*>(currentItem());
  if(it && it->isEntryItem() && entry_ == static_cast<EntryItem*>(it)->entry()) {
    return;
  }

  // have to find a group whose field is the same as currently shown
  if(m_groupBy.isEmpty()) {
    myDebug() << "GroupView::slotSetSelected() - no group field" << endl;
    return;
  }

  const Data::EntryGroup* group = 0;
  for(PtrVector<Data::EntryGroup>::ConstIterator it = entry_->groups().begin(); it != entry_->groups().end(); ++it) {
    if(it->fieldName() == m_groupBy) {
      group = it.ptr();
      break;
    }
  }
  if(!group) {
    myDebug() << "GroupView::slotSetSelected() - entry is not in any current groups!" << endl;
    return;
  }

  EntryGroupItem* groupItem = m_groupDict.find(group->groupName());
  if(!groupItem) {
    return;
  }

  clearSelection();
  for(TQListViewItem* item = groupItem->firstChild(); item; item = item->nextSibling()) {
    EntryItem* entryItem = static_cast<EntryItem*>(item);
    if(entryItem->entry() == entry_) {
      blockSignals(true);
      setSelected(item, true);
      setCurrentItem(item);
      blockSignals(false);
      ensureItemVisible(item);
      return;
    }
  }
}

void GroupView::slotExpandAll(int depth_/*=-1*/) {
  if(childCount() == 0) {
    return;
  }
  setSiblingsOpen(depth_, true);
}

void GroupView::slotCollapseAll(int depth_/*=-1*/) {
  if(childCount() == 0) {
    return;
  }
  setSiblingsOpen(depth_, false);
}

void GroupView::setSiblingsOpen(int depth_, bool open_) {
  TQListViewItem* item = 0;

  if(depth_ == -1) {
    item = currentItem();
    if(!item) {
      return;
    }
    depth_ = item->depth();
  }

  switch(depth_) {
    case 0:
      item = firstChild();
      break;

    case 1:
      item = firstChild()->firstChild();
      break;

    default:
      return;
  }

  for( ; item; item = item->nextSibling()) {
    item->setOpen(open_);
  }
}

void GroupView::contextMenuRequested(TQListViewItem* item_, const TQPoint& point_, int) {
  if(!item_) {
    return;
  }

  KPopupMenu menu(this);
  GUI::ListViewItem* item = static_cast<GUI::ListViewItem*>(item_);
  if(item->isEntryGroupItem()) {
    menu.insertItem(SmallIconSet(TQString::fromLatin1("2downarrow")),
                    i18n("Expand All Groups"), this, TQT_SLOT(slotExpandAll()));
    menu.insertItem(SmallIconSet(TQString::fromLatin1("2uparrow")),
                    i18n("Collapse All Groups"), this, TQT_SLOT(slotCollapseAll()));
    menu.insertItem(SmallIconSet(TQString::fromLatin1("filter")),
                    i18n("Filter by Group"), this, TQT_SLOT(slotFilterGroup()));
  } else if(item->isEntryItem()) {
    Controller::self()->plugEntryActions(&menu);
  }
  menu.exec(point_);
}

void GroupView::slotCollapsed(TQListViewItem* item_) {
  // only change icon for group items
  if(static_cast<GUI::ListViewItem*>(item_)->isEntryGroupItem()) {
    if(item_->text(0) == i18n(Data::Collection::s_emptyGroupTitle)) {
      item_->setPixmap(0, SmallIcon(TQString::fromLatin1("folder_red")));
    } else {
      item_->setPixmap(0, m_groupClosedPixmap);
    }
    static_cast<GUI::ListViewItem*>(item_)->clear();
  }
}

void GroupView::slotExpanded(TQListViewItem* item_) {
  EntryGroupItem* item = static_cast<EntryGroupItem*>(item_);
  // only change icon for group items
  if(!item->isEntryGroupItem()) {
    return;
  }

  setUpdatesEnabled(false);
  if(item->text(0) == i18n(Data::Collection::s_emptyGroupTitle)) {
    item->setPixmap(0, SmallIcon(TQString::fromLatin1("folder_red_open")));
  } else {
    item->setPixmap(0, m_groupOpenPixmap);
  }

  Data::EntryGroup* group = item->group();
  if(!group) {
    myDebug() << "GroupView::slotExpanded() - no entry group! - " << item->text(0) << endl;
  } else {
    for(Data::EntryVecIt entryIt = group->begin(); entryIt != group->end(); ++entryIt) {
      new EntryItem(item, entryIt);
    }
  }

  setUpdatesEnabled(true);
  triggerUpdate();
}

void GroupView::addCollection(Data::CollPtr coll_) {
//  myDebug() << "GroupView::addCollection()" << endl;
  if(!coll_) {
    kdWarning() << "GroupView::addCollection() - null coll pointer!" << endl;
    return;
  }

  m_coll = coll_;
  // if the collection doesn't have the grouped field, and it's not the pseudo-group,
  // change it to default
  if(m_groupBy.isEmpty() || (!coll_->hasField(m_groupBy) && m_groupBy != Data::Collection::s_peopleGroupName)) {
    m_groupBy = coll_->defaultGroupField();
  }

  // when the coll gets set for the first time, the pixmaps need to be updated
  if((m_coll->hasField(m_groupBy) && m_coll->fieldByName(m_groupBy)->formatFlag() == Data::Field::FormatName)
     || m_groupBy == Data::Collection::s_peopleGroupName) {
    m_groupOpenPixmap = UserIcon(TQString::fromLatin1("person-open"));
    m_groupClosedPixmap = UserIcon(TQString::fromLatin1("person"));
  }

  Data::FieldPtr f = coll_->fieldByName(TQString::fromLatin1("title"));
  if(f) {
    setComparison(0, ListViewComparison::create(f));
  }

  updateHeader();
  populateCollection();

  slotCollapseAll();
//  myDebug() << "GroupView::addCollection - done" << endl;
}

void GroupView::setGroupField(const TQString& groupField_) {
//  myDebug() << "GroupView::setGroupField - " << groupField_ << endl;
  if(groupField_.isEmpty() || groupField_ == m_groupBy) {
    return;
  }

  m_groupBy = groupField_;
  if(!m_coll) {
    return; // can't do anything yet, but still need to set the variable
  }
  if((m_coll->hasField(groupField_) && m_coll->fieldByName(groupField_)->formatFlag() == Data::Field::FormatName)
     || groupField_ == Data::Collection::s_peopleGroupName) {
    m_groupOpenPixmap = UserIcon(TQString::fromLatin1("person-open"));
    m_groupClosedPixmap = UserIcon(TQString::fromLatin1("person"));
  } else {
    m_groupOpenPixmap = SmallIcon(TQString::fromLatin1("folder_open"));
    m_groupClosedPixmap = SmallIcon(TQString::fromLatin1("folder"));
  }
  updateHeader();
  populateCollection();
}

void GroupView::populateCollection() {
  if(!m_coll) {
    return;
  }

//  myDebug() << "GroupView::populateCollection() - " << m_groupBy << endl;
  if(m_groupBy.isEmpty()) {
    m_groupBy = m_coll->defaultGroupField();
  }

  setUpdatesEnabled(false);
  clear(); // delete all groups
  m_groupDict.clear();

  // if there's no group field, just return
  if(m_groupBy.isEmpty()) {
    setUpdatesEnabled(true);
    return;
  }

  Data::EntryGroupDict* dict = m_coll->entryGroupDictByName(m_groupBy);
  if(!dict) { // could happen if m_groupBy is non empty, but there are no entries with a value
    return;
  }

  // iterate over all the groups in the dict
  // e.g. if the dict is "author", loop over all the author groups
  for(TQDictIterator<Data::EntryGroup> it(*dict); it.current(); ++it) {
    addGroup(it.current());
  }

  setUpdatesEnabled(true);
  triggerUpdate();
}

void GroupView::slotFilterGroup() {
  const GUI::ListViewItemList& items = selectedItems();
  GUI::ListViewItem* item = items.getFirst();
  // only works for entry groups
  if(!item || !item->isEntryGroupItem()) {
    return;
  }

  FilterPtr filter = new Filter(Filter::MatchAny);

  for(GUI::ListViewItemListIt it(items); it.current(); ++it) {
    if(static_cast<EntryGroupItem*>(it.current())->count() == 0) { //ignore empty items
      continue;
    }
    // need to check for people group
    if(m_groupBy == Data::Collection::s_peopleGroupName) {
      Data::EntryPtr entry = static_cast<EntryItem*>(it.current()->firstChild())->entry();
      Data::FieldVec fields = entry->collection()->peopleFields();
      for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) {
        filter->append(new FilterRule(fIt->name(), it.current()->text(0), FilterRule::FuncContains));
      }
    } else {
      TQString s = it.current()->text(0);
      if(s != i18n(Data::Collection::s_emptyGroupTitle)) {
        filter->append(new FilterRule(m_groupBy, it.current()->text(0), FilterRule::FuncContains));
      }
    }
  }

  if(!filter->isEmpty()) {
    emit signalUpdateFilter(filter);
  }
}

// this gets called when header() is clicked, so cycle through
void GroupView::setSorting(int col_, bool asc_) {
  if(asc_ && !m_notSortedYet) { // cycle through after ascending
    if(sortStyle() == ListView::SortByText) {
      setSortStyle(ListView::SortByCount);
    } else {
      setSortStyle(ListView::SortByText);
    }
  }
  updateHeader();
  m_notSortedYet = false;
  ListView::setSorting(col_, asc_);
}

void GroupView::addField(Data::CollPtr, Data::FieldPtr) {
  resetComparisons();
}

void GroupView::modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr newField_) {
  if(newField_->name() == m_groupBy) {
    updateHeader(newField_);
  }
  // if the grouping changed at all, our groups got deleted out from under us
  // so check first pointer, too. The groups could be deleted if the format type
  // changes, too, so not enough just to check group flag
  if(childCount() > 0 && static_cast<EntryGroupItem*>(firstChild())->group() == 0) {
    populateCollection();
  }
  resetComparisons();
}

void GroupView::removeField(Data::CollPtr, Data::FieldPtr) {
  resetComparisons();
}

void GroupView::updateHeader(Data::FieldPtr field_/*=0*/) {
  TQString t = field_ ? field_->title() : groupTitle();
  if(sortStyle() == ListView::SortByText) {
    setColumnText(0, t);
  } else {
    setColumnText(0, i18n("%1 (Sort by Count)").arg(t));
  }
}

TQString GroupView::groupTitle() {
  TQString title;
  if(!m_coll || m_groupBy.isEmpty()) {
    title = i18n("Group Name Header", "Group");
  } else {
    Data::FieldPtr f = m_coll->fieldByName(m_groupBy);
    if(f) {
      title = f->title();
    } else if(m_groupBy == Data::Collection::s_peopleGroupName) {
      title = i18n("People");
    }
  }
  return title;
}

void GroupView::resetComparisons() {
  if(!m_coll) {
    return;
  }
  Data::FieldPtr f = m_coll->fieldByName(TQString::fromLatin1("title"));
  if(f) {
    setComparison(0, ListViewComparison::create(f));
  }
}

#include "groupview.moc"
