/*
 * This file is part of the KFTPGrabber project
 *
 * Copyright (C) 2003-2006 by the KFTPGrabber developers
 * Copyright (C) 2003-2006 Jernej Kos <kostko@jweb-network.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
 * NON-INFRINGEMENT.  See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of portions of this program with the
 * OpenSSL library under certain conditions as described in each
 * individual source file, and distribute linked combinations
 * including the two.
 *
 * You must obey the GNU General Public License in all respects
 * for all of the code used other than OpenSSL.  If you modify
 * file(s) with this exception, you may extend this exception to your
 * version of the file(s), but you are not obligated to do so.  If you
 * do not wish to do so, delete this exception statement from your
 * version.  If you delete this exception statement from all source
 * files in the program, then also delete it here.
 */
#include "browser/detailsview.h"
#include "browser/treeview.h"
#include "browser/view.h"
#include "browser/locationnavigator.h"
#include "browser/dirlister.h"
#include "browser/actions.h"

#include "misc/config.h"
#include "misc/kftpapi.h"
#include "misc/filter.h"

#include "kftpqueue.h"

#include <qheader.h>
#include <qpainter.h>

#include <kpopupmenu.h>
#include <kurldrag.h>

using namespace KFTPCore::Filter;

namespace KFTPWidgets {

namespace Browser {

DetailsView::DetailsView(QWidget *parent, View *view, KFTPSession::Session *session)
  : KFileDetailView(parent, 0),
    m_view(view),
    m_treeView(0),
    m_refreshing(false),
    m_shouldDisableResize(true),
    m_autoResizeEnabled(true)
{
  m_resizeTimer = new QTimer(this);
  connect(m_resizeTimer, SIGNAL(timeout()), this, SLOT(updateColumnWidths()));
  
  m_dirLister = new DirLister(this);
  m_dirLister->setSession(session);
  
  connect(m_dirLister, SIGNAL(clear()), this, SLOT(slotClear()));
  connect(m_dirLister, SIGNAL(completed()), this, SLOT(slotCompleted()));
  connect(m_dirLister, SIGNAL(deleteItem(KFileItem*)), this, SLOT(slotDeleteItem(KFileItem*)));
  connect(m_dirLister, SIGNAL(refreshItems()), this, SLOT(slotRefreshItems()));
  connect(m_dirLister, SIGNAL(siteChanged(const KURL&)), this, SLOT(slotSiteChanged(const KURL&)));
  
  m_navigator = new LocationNavigator(this);
  
  connect(m_navigator, SIGNAL(urlChanged(const KURL&)), this, SLOT(slotUrlChanged(const KURL&)));
  
  connect(this, SIGNAL(executed(QListViewItem*)), this, SLOT(slotItemExecuted()));
  connect(this, SIGNAL(returnPressed(QListViewItem*)), this, SLOT(slotItemExecuted()));
  connect(this, SIGNAL(contextMenu(KListView*, QListViewItem*, const QPoint&)), this, SLOT(slotContextMenu(KListView*, QListViewItem*, const QPoint&)));
  connect(this, SIGNAL(itemRenamed(QListViewItem*, const QString&, int)), this, SLOT(slotItemRenamed(QListViewItem*, const QString&)));
  
  // Setup the header
  QHeader *viewHeader = header();
  viewHeader->setResizeEnabled(true);
  viewHeader->setMovingEnabled(false);
  
  connect(viewHeader, SIGNAL(sizeChange(int, int, int)), this, SLOT(slotHeaderResized(int)));
  
  // Set column width
  setColumnWidthMode(NameColumn, QListView::Manual);
  setColumnWidthMode(SizeColumn, QListView::Manual);
  setColumnWidthMode(DateColumn, QListView::Manual);
  setColumnWidthMode(PermissionsColumn, QListView::Manual);
  
  // Set column alignments
  setColumnAlignment(SizeColumn, Qt::AlignRight);
  setColumnAlignment(DateColumn, Qt::AlignHCenter);
  setColumnAlignment(PermissionsColumn, Qt::AlignHCenter);
  
  if (KFTPCore::Config::showOwnerGroup()) {
    setColumnAlignment(OwnerColumn, Qt::AlignHCenter);
    setColumnAlignment(GroupColumn, Qt::AlignHCenter);
  } else {
    // Only show owner/group if set in the config
    removeColumn(4);
    removeColumn(4);
  }
  
  setColumnWidth(NameColumn, 140);
  setColumnWidth(DateColumn, 100);
  
  setAcceptDrops(true);
  setSelectionMode(KFile::Extended);
  setHScrollBarMode(QScrollView::AlwaysOff);
  
  // Set the defaults
  setHomeUrl(KURL(KFTPCore::Config::defLocalDir()));
  setShowHidden(KFTPCore::Config::showHiddenFiles());
}

DetailsView::~DetailsView()
{
}

const KURL &DetailsView::url() const
{
  return m_navigator->url();
}

const QValueList<LocationNavigator::Element> DetailsView::history(int &index) const
{
  return m_navigator->history(index);
}

bool DetailsView::isSelected(const KFileItem *i) const
{
  if (!i)
    return false;
  
  const KFileListViewItem *item = static_cast<const KFileListViewItem*>(i->extraData(this));
  return (item && item->isSelected() && item->isVisible());
}

void DetailsView::setTreeView(TreeView *tree)
{
  m_treeView = tree;
  
  connect(m_treeView, SIGNAL(pathChanged(const KURL&)), this, SLOT(openUrl(const KURL&)));
}

void DetailsView::setHomeUrl(const KURL &url)
{
  m_navigator->setHomeUrl(url);
}

void DetailsView::goBack()
{
  m_navigator->goBack();
}
    
void DetailsView::goForward()
{
  m_navigator->goForward();
}

void DetailsView::goUp()
{
  m_navigator->goUp();
}

void DetailsView::goHome()
{
  m_navigator->goHome();
}

void DetailsView::endItemUpdates()
{
  const KFileListViewItem *item = static_cast<const KFileListViewItem*>(firstChild());
  if (item)
    setCurrentItem(item->fileInfo());
    
  int index = 0;
  const QValueList<LocationNavigator::Element> history = m_navigator->history(index);
  if (!history.isEmpty()) {
    KFileView::setCurrentItem(history[index].currentFilename());
    KFileDetailView::setSelected(currentFileItem(), true);
    
    // Scroll the contents to last known coordinates
    setContentsPos(history[index].contentsX(), history[index].contentsY());
  }
  
  m_treeView->openUrl(m_navigator->url());
  m_treeView->endUpdate(m_navigator->url());
  setFocus();
}

void DetailsView::openUrl(const KURL &url)
{
  m_navigator->setUrl(url);
}

void DetailsView::slotClear()
{
  clearView();
}

void DetailsView::slotDeleteItem(KFileItem *item)
{
  removeItem(item);
  
  if (item->isDir())
    m_treeView->removeFolder(item->url());
}

void DetailsView::slotCompleted()
{
  m_refreshing = true;
  
  clearView();
  
  KFileItemList items(m_dirLister->items());
  KFileItemListIterator i(items);
  
  KFileItem *item = 0;
  while ((item = i.current()) != 0) {
    insertItem(item);
    
    if (item->isDir())
      m_treeView->createFolder(item->url(), item->pixmap(16));
    
    ++i;
  }
  
  endItemUpdates();
  m_refreshing = false;
  
  m_view->updateActions();
}

void DetailsView::slotRefreshItems()
{
  QTimer::singleShot(0, this, SLOT(reload()));
}

void DetailsView::reload()
{
  fetchLocation(m_navigator->url(), true);
}

void DetailsView::slotUrlChanged(const KURL &url)
{
  fetchLocation(url);
}

void DetailsView::slotSiteChanged(const KURL &url)
{
  m_navigator->clear();
  m_treeView->resetView(url);
}

void DetailsView::fetchLocation(const KURL &url, bool reload)
{
  m_dirLister->setShowingDotFiles(m_showHidden);
  m_dirLister->fetchLocation(url, reload);
}

void DetailsView::slotContentsMoving(int x, int y)
{
  if (!m_refreshing)
    emit contentsMoved(x, y);
}

void DetailsView::slotItemExecuted()
{
  KFileItem *item = currentFileItem();
  
  if (item) {
    if (item->isDir())
      openUrl(item->url());
  }
}

void DetailsView::slotHeaderResized(int section)
{
  if (m_autoResizeEnabled && m_shouldDisableResize && section == 0) {
    setHScrollBarMode(QScrollView::Auto);
    m_autoResizeEnabled = false;
  }
}

void DetailsView::resizeContents(int width, int height)
{
  m_shouldDisableResize = false;
  
  KFileDetailView::resizeContents(width, height);
  
  // Update the column widths
  if (m_autoResizeEnabled) {
    m_resizeTimer->stop();
    m_resizeTimer->start(50, true);
  }
}

void DetailsView::resizeEvent(QResizeEvent *event)
{
  m_shouldDisableResize = false;
  
  KFileDetailView::resizeEvent(event);
  
  // Update the column widths
  if (m_autoResizeEnabled) {
    m_resizeTimer->stop();
    m_resizeTimer->start(50, true);
  }
}

void DetailsView::updateColumnWidths()
{
  // The code below is based on Dolphin, Copyright (C) 2006 by Peter Penz
  const int columnCount = columns();
  int requiredWidth = 0;
  
  for (int i = 1; i < columnCount; ++i) {
    // When a directory contains no items, a minimum width for
    // the column must be available, so that the header is readable.
    int columnWidth = 64;
    QFontMetrics fontMetrics(font());
    
    for (QListViewItem* item = firstChild(); item != 0; item = item->nextSibling()) {
      const int width = item->width(fontMetrics, this, i);
      
      if (width > columnWidth) {
        columnWidth = width;
      }
    }
    
    // Add custom margin
    columnWidth += 16;
    setColumnWidth(i, columnWidth);
    requiredWidth += columnWidth;
  }

  // Resize the first column in a way that the whole available width is used
  int firstColumnWidth = visibleWidth() - requiredWidth;
  if (firstColumnWidth < 128) {
    firstColumnWidth = 128;
  }
  
  setColumnWidth(0, firstColumnWidth);
  m_shouldDisableResize = true;
}

void DetailsView::insertItem(KFileItem *fileItem)
{
  const ActionChain *actionChain = Filters::self()->process(fileItem->url(), fileItem->size(), fileItem->isDir());
  const Action *action;
  
  if ((actionChain && actionChain->getAction(Action::Hide)))
    return;
  
  KFileView::insertItem(fileItem);
  
  ListViewItem *item = new ListViewItem(this, fileItem);
  
  if (actionChain && (action = actionChain->getAction(Action::Colorize)))
    item->setColor(action->value().toColor());
  
  QDir::SortSpec spec = KFileView::sorting();
  if (spec & QDir::Time) {
    item->setKey(sortingKey(fileItem->time(KIO::UDS_MODIFICATION_TIME), fileItem->isDir(), spec));
  } else if (spec & QDir::Size) {
    item->setKey(sortingKey(fileItem->size(), fileItem->isDir(), spec));
  } else {
    item->setKey(sortingKey(fileItem->text(), fileItem->isDir(), spec));
  }
  
  fileItem->setExtraData(this, item);
}

void DetailsView::slotContextMenu(KListView*, QListViewItem *i, const QPoint &p)
{
  m_view->updateActions();
  
  // Create the popup menu
  KPopupMenu *menu = new KPopupMenu(this);
  
  Actions *actions = m_view->m_actions;

  // Always show create directory
  actions->m_createDirAction->plug(menu);
  menu->insertSeparator();

  // If nothing is selected, show the navigation menus
  if (!i) {
    actions->m_goUpAction->plug(menu);
    actions->m_goBackAction->plug(menu);
    actions->m_goForwardAction->plug(menu);
    actions->m_reloadAction->plug(menu);
  } else {
    actions->m_transferAction->plug(menu);
    actions->m_queueTransferAction->plug(menu);
    actions->m_renameAction->plug(menu);
    actions->m_deleteAction->plug(menu);
    actions->m_shredAction->plug(menu);
    actions->m_fileEditAction->plug(menu);
    actions->m_verifyAction->plug(menu);
    menu->insertSeparator();
    actions->m_copyAction->plug(menu);
    actions->m_pasteAction->plug(menu);
    menu->insertSeparator();
    actions->m_filterActions->plug(menu);
  }

  // Always show properties
  menu->insertSeparator();
  actions->m_propsAction->plug(menu);

  menu->exec(p);
}

void DetailsView::slotItemRenamed(QListViewItem *item, const QString &name)
{
  KFileItem *fileItem = static_cast<KFileListViewItem*>(item)->fileInfo();
  m_view->rename(fileItem->url(), name);
}

QDragObject *DetailsView::dragObject()
{
  KURLDrag *object = static_cast<KURLDrag*>(KFileDetailView::dragObject());
  
  // Add some metadata
  const KFileItemList *list = KFileView::selectedItems();
  
  if (list) {
    KFileItemListIterator i(*list);
    KFileItem *item;
    
    while ((item = i.current()) != 0) {
      QString type = item->isDir() ? "D" : "F";
      object->metaData().insert(item->url().htmlURL().local8Bit(), type + ":" + KIO::number(item->size()));
      ++i;
    }
  }
  
  return object;
}

bool DetailsView::acceptDrag(QDropEvent *event) const
{
  return KURLDrag::canDecode(event) &&
         (event->action() == QDropEvent::Copy ||
          event->action() == QDropEvent::Move ||
          event->action() == QDropEvent::Link) &&
         event->source() != this;
}

void DetailsView::contentsDropEvent(QDropEvent *event)
{
  if (!acceptDrag(event))
    return;
  
  KIO::MetaData meta;
  KURL::List urls;
  KURLDrag::decode(event, urls, meta);
  
  meta.insert("DestURL", url().url());
  KFTPQueue::Manager::self()->insertTransfer(new KURLDrag(urls, meta, this, name()));
}

KFileItem *DetailsView::fileItem(const QString &filename)
{
  if (!filename.isNull()) {
    KFileItem *item;
    
    for (item = firstFileItem(); item; item = nextItem(item)) {
      if (item->name() == filename)
        return item;
    }
  }
  
  return 0;
}

void DetailsView::setItemVisibility(KFileItem *item, int visibility)
{
  ListViewItem *i = static_cast<ListViewItem*>(item->extraData(this));
  
  if (i) {
    if (visibility != 0 && visibility != 1)
      visibility = !i->isVisible();
    
    i->setVisible(visibility);
  }
}

void DetailsView::setItemColor(KFileItem *item, const QColor &text, const QColor &background)
{
  ListViewItem *i = static_cast<ListViewItem*>(item->extraData(this));
  
  if (i) {
    i->setColor(text);
    i->setBackground(background);
    i->repaint();
  }
}

void DetailsView::markItem(KFileItem *item)
{
  ListViewItem *i = static_cast<ListViewItem*>(item->extraData(this));
  
  if (i) {
    i->markItem(!i->marked());
  }
}

void DetailsView::markItem(const QString &filename)
{
  if (KFileItem *item = fileItem(filename))
    markItem(item);
}

void DetailsView::unmarkItems()
{
  KFileItem *item;
    
  for (item = firstFileItem(); item; item = nextItem(item)) {
    static_cast<ListViewItem*>(item->extraData(this))->markItem(false);
  }
}

DetailsView::ListViewItem::ListViewItem(QListView *parent, KFileItem *fileItem)
  : KFileListViewItem(parent, fileItem),
    m_marked(false)
{
  if (fileItem->isDir() && !KFTPCore::Config::showDirectorySize()) {
    setText(SizeColumn, " - ");
  } else {
    QString sizeText;
    sizeText = KFTPCore::Config::showSizeInBytes() ? KIO::number(fileItem->size()) : KIO::convertSize(fileItem->size());
    sizeText.append(" ");
    
    setText(SizeColumn, sizeText);
  }
}

void DetailsView::ListViewItem::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment)
{
  QColorGroup colorGroup(cg);
  QColor textColor = colorGroup.text();

  // Set custom file item color if set
  if (m_textColor.isValid())
    colorGroup.setColor(QColorGroup::Text, m_textColor);
  
  // Set custom file item font
  QFont font = p->font();
  font.setBold(m_marked);
  p->setFont(font);
  
  if (m_backgroundColor.isValid()) {
    colorGroup.setColor(QColorGroup::Base, m_backgroundColor);
    
    // Override the KListViewItem since it resets the background color
    QListViewItem::paintCell(p, colorGroup, column, width, alignment);
  } else {
    KFileListViewItem::paintCell(p, colorGroup, column, width, alignment);
  }
  
  if (column < listView()->columns() - 1) {
    // Draw a separator between columns
    p->setPen(KGlobalSettings::buttonBackground());
    p->drawLine(width - 1, 0, width - 1, height() - 1);
  }
}

void DetailsView::ListViewItem::markItem(bool marked)
{
  if (m_marked != marked) {
    m_marked = marked;
    repaint();
  }
}

}

}

#include "detailsview.moc"
