/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2008-11-21
 * Description : Batch Queue Manager items list.
 *
 * Copyright (C) 2008-2009 by Gilles Caulier <caulier dot gilles at gmail dot com>
 *
 * 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, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * ============================================================ */

#define ICONSIZE 64

#include "queuelist.h"
#include "queuelist.moc"

// Qt includes

#include <QPainter>
#include <QDragEnterEvent>
#include <QUrl>
#include <QFileInfo>
#include <QHeaderView>
#include <QTimer>

// KDE includes

#include <klocale.h>
#include <kiconloader.h>
#include <kdebug.h>
#include <kaction.h>
#include <kmenu.h>
#include <kactioncollection.h>

// Local includes

#include "albumdb.h"
#include "thumbnailsize.h"
#include "thumbnailloadthread.h"
#include "renamecustomizer.h"
#include "ddragobjects.h"
#include "queuemgrwindow.h"
#include "queuetooltip.h"

namespace Digikam
{

class QueueListViewItemPriv
{

public:

    QueueListViewItemPriv()
    {
        done = false;
    }

    bool      done;

    QString   destFileName;

    QPixmap   preview;

    ImageInfo info;
};

QueueListViewItem::QueueListViewItem(QTreeWidget *view, const ImageInfo& info)
                 : QTreeWidgetItem(view), d(new QueueListViewItemPriv)
{
    setThumb(SmallIcon("image-x-generic", KIconLoader::SizeLarge, KIconLoader::DisabledState));
    setInfo(info);
}

QueueListViewItem::~QueueListViewItem()
{
    delete d;
}

void QueueListViewItem::setInfo(const ImageInfo& info)
{
    d->info = info;
    setText(1, d->info.name());
}

ImageInfo QueueListViewItem::info() const
{
    return d->info;
}

void QueueListViewItem::setThumb(const QPixmap& pix)
{
    QPixmap pixmap(ICONSIZE+2, ICONSIZE+2);
    pixmap.fill(Qt::transparent);
    QPainter p(&pixmap);
    p.drawPixmap((pixmap.width()/2) - (pix.width()/2), (pixmap.height()/2) - (pix.height()/2), pix);
    d->preview = pixmap;
    setIcon(0, QIcon(d->preview));
}

void QueueListViewItem::setProgressIcon(const QPixmap& icon)
{
    QPixmap preview = d->preview;
    QPixmap mask(preview.size());
    mask.fill(QColor(128, 128, 128, 192));
    QPainter p(&preview);
    p.drawPixmap(0, 0, mask);
    p.drawPixmap((preview.width()/2) - (icon.width()/2), (preview.height()/2) - (icon.height()/2), icon);
    setIcon(0, QIcon(preview));
}

void QueueListViewItem::setCanceled()
{
    setIcon(0, QIcon(d->preview));
    setIcon(1, SmallIcon("dialog-cancel"));
    d->done = false;
}

void QueueListViewItem::setFailed()
{
    setIcon(0, QIcon(d->preview));
    setIcon(1, SmallIcon("dialog-error"));
    d->done = false;
}

void QueueListViewItem::setDone()
{
    setIcon(0, QIcon(d->preview));
    setIcon(1, SmallIcon("dialog-ok"));
    d->done = true;
}

bool QueueListViewItem::isDone()
{
    return d->done;
}

void QueueListViewItem::reset()
{
    setIcon(0, QIcon(d->preview));
    setIcon(1, QIcon());
    d->done = false;
}

void QueueListViewItem::setDestFileName(const QString& str)
{
    d->destFileName = str;
    setText(2, d->destFileName);
}

QString QueueListViewItem::destFileName() const
{
    return d->destFileName;
}

QString QueueListViewItem::destBaseName() const
{
    QFileInfo fi(d->destFileName);
    return fi.completeBaseName();
}

QString QueueListViewItem::destSuffix() const
{
    QFileInfo fi(d->destFileName);
    return fi.suffix();
}

// ---------------------------------------------------------------------------

class QueueListViewPriv
{

public:

    enum RemoveItemsType
    {
        ItemsSelected = 0,
        ItemsDone,
        ItemsAll
    };

public:

    QueueListViewPriv()
    {
        showTips        = false;
        toolTipTimer    = 0;
        toolTip         = 0;
        toolTipItem     = 0;
        thumbLoadThread = ThumbnailLoadThread::defaultThread();
    }

    bool                 showTips;

    QTimer              *toolTipTimer;

    ThumbnailLoadThread *thumbLoadThread;

    QueueSettings        settings;

    AssignedBatchTools   toolsList;

    QueueToolTip        *toolTip;

    QueueListViewItem   *toolTipItem;
};

QueueListView::QueueListView(QWidget *parent)
             : QTreeWidget(parent), d(new QueueListViewPriv)
{
    setIconSize(QSize(ICONSIZE, ICONSIZE));
    setSelectionMode(QAbstractItemView::ExtendedSelection);
    setWhatsThis(i18n("This is the list of images to batch process."));

    setAcceptDrops(true);
    viewport()->setAcceptDrops(true);
    setDropIndicatorShown(true);
    setDragEnabled(true);
    viewport()->setMouseTracking(true);

    setSortingEnabled(false);
    setAllColumnsShowFocus(true);
    setRootIsDecorated(false);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setColumnCount(3);
    setContextMenuPolicy(Qt::CustomContextMenu);

    QStringList titles;
    titles.append(i18n("Thumbnail"));
    titles.append(i18n("File Name"));
    titles.append(i18n("Target"));
    setHeaderLabels(titles);
    header()->setResizeMode(0, QHeaderView::ResizeToContents);
    header()->setResizeMode(1, QHeaderView::Stretch);
    header()->setResizeMode(2, QHeaderView::Stretch);

    d->toolTip      = new QueueToolTip(this);
    d->toolTipTimer = new QTimer(this);

    // -----------------------------------------------------------

    connect(d->thumbLoadThread, SIGNAL(signalThumbnailLoaded(const LoadingDescription&, const QPixmap&)),
            this, SLOT(slotThumbnailLoaded(const LoadingDescription&, const QPixmap&)));

    connect(this, SIGNAL(customContextMenuRequested(const QPoint&)),
            this, SLOT(slotContextMenu()));

    connect(d->toolTipTimer, SIGNAL(timeout()),
            this, SLOT(slotToolTip()));
}

QueueListView::~QueueListView()
{
    delete d->toolTip;
    delete d;
}

Qt::DropActions QueueListView::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

QMimeData* QueueListView::mimeData(const QList<QTreeWidgetItem*> items) const
{
    KUrl::List urls;
    KUrl::List kioURLs;
    QList<int> albumIDs;
    QList<int> imageIDs;

    foreach(QTreeWidgetItem* itm, items)
    {
        QueueListViewItem* vitem = dynamic_cast<QueueListViewItem*>(itm);
        if (vitem)
        {
            urls.append(vitem->info().fileUrl());
            kioURLs.append(vitem->info().databaseUrl());
            albumIDs.append(vitem->info().albumId());
            imageIDs.append(vitem->info().id());
        }
    }

    DItemDrag *mimeData = new DItemDrag(urls, kioURLs, albumIDs, imageIDs);
    return mimeData;
}

void QueueListView::startDrag(Qt::DropActions /*supportedActions*/)
{
    QList<QTreeWidgetItem*> items = selectedItems();
    if (items.isEmpty())
        return;

    QPixmap icon(DesktopIcon("image-jp2", 48));
    int w = icon.width();
    int h = icon.height();

    QPixmap pix(w+4,h+4);
    QString text(QString::number(items.count()));

    QPainter p(&pix);
    p.fillRect(0, 0, pix.width()-1, pix.height()-1, QColor(Qt::white));
    p.setPen(QPen(Qt::black, 1));
    p.drawRect(0, 0, pix.width()-1, pix.height()-1);
    p.drawPixmap(2, 2, icon);
    QRect r = p.boundingRect(2, 2, w, h, Qt::AlignLeft|Qt::AlignTop, text);
    r.setWidth(qMax(r.width(), r.height()));
    r.setHeight(qMax(r.width(), r.height()));
    p.fillRect(r, QColor(0, 80, 0));
    p.setPen(Qt::white);
    QFont f(font());
    f.setBold(true);
    p.setFont(f);
    p.drawText(r, Qt::AlignCenter, text);
    p.end();

    QDrag *drag = new QDrag(this);
    drag->setMimeData(mimeData(items));
    drag->setPixmap(pix);
    drag->exec();
}

void QueueListView::dragEnterEvent(QDragEnterEvent *e)
{
    QTreeWidget::dragEnterEvent(e);
    e->accept();
}

void QueueListView::dragMoveEvent(QDragMoveEvent *e)
{
    int        albumID;
    QList<int> albumIDs;
    QList<int> imageIDs;
    KUrl::List urls;
    KUrl::List kioURLs;

    if (DItemDrag::decode(e->mimeData(), urls, kioURLs, albumIDs, imageIDs) ||
        DAlbumDrag::decode(e->mimeData(), urls, albumID) ||
        DTagDrag::canDecode(e->mimeData()))
    {
        if (DItemDrag::decode(e->mimeData(), urls, kioURLs, albumIDs, imageIDs))
        {
            ImageInfoList imageInfoList;

            for (QList<int>::const_iterator it = imageIDs.constBegin();
                it != imageIDs.constEnd(); ++it)
            {
                ImageInfo info(*it);
                if (!findItemByInfo(info))
                    imageInfoList.append(info);
            }

            if (!imageInfoList.isEmpty())
            {
                QTreeWidget::dragMoveEvent(e);
                e->accept();
                return;
            }
        }
    }
    e->ignore();
}

void QueueListView::dropEvent(QDropEvent *e)
{
    int        albumID;
    QList<int> albumIDs;
    QList<int> imageIDs;
    KUrl::List urls;
    KUrl::List kioURLs;

    if (DItemDrag::decode(e->mimeData(), urls, kioURLs, albumIDs, imageIDs))
    {
        ImageInfoList imageInfoList;

        for (QList<int>::const_iterator it = imageIDs.constBegin();
             it != imageIDs.constEnd(); ++it)
        {
            ImageInfo info(*it);
            if (!findItemByInfo(info))
            {
                imageInfoList.append(info);
            }
        }

        if (!imageInfoList.isEmpty())
        {
            slotAddItems(imageInfoList);
            e->acceptProposedAction();

            QueueListView* vitem = dynamic_cast<QueueListView*>(e->source());
            if (vitem && vitem != this)
            {
                foreach(ImageInfo info, imageInfoList)
                    vitem->removeItemByInfo(info);
            }
        }
    }
    else if (DAlbumDrag::decode(e->mimeData(), urls, albumID))
    {
        QList<qlonglong> itemIDs = DatabaseAccess().db()->getItemIDsInAlbum(albumID);
        ImageInfoList imageInfoList;

        for (QList<qlonglong>::const_iterator it = itemIDs.constBegin();
             it != itemIDs.constEnd(); ++it)
        {
            ImageInfo info(*it);
            if (!findItemByInfo(info))
            {
                imageInfoList.append(info);
            }
        }

        if (!imageInfoList.isEmpty())
        {
            slotAddItems(imageInfoList);
            e->acceptProposedAction();
        }
    }
    else if(DTagDrag::canDecode(e->mimeData()))
    {
        int tagID;
        if (!DTagDrag::decode(e->mimeData(), tagID))
            return;

        QList<qlonglong> itemIDs = DatabaseAccess().db()->getItemIDsInTag(tagID, true);
        ImageInfoList imageInfoList;

        for (QList<qlonglong>::const_iterator it = itemIDs.constBegin();
             it != itemIDs.constEnd(); ++it)
        {
            ImageInfo info(*it);
            if (!findItemByInfo(info))
            {
                imageInfoList.append(info);
            }
        }

        if (!imageInfoList.isEmpty())
        {
            slotAddItems(imageInfoList);
            e->acceptProposedAction();
        }
    }
    else
    {
        e->ignore();
    }

    emit signalQueueContentsChanged();
}

void QueueListView::setEnableToolTips(bool val)
{
    d->showTips = val;
    if (!val)
        hideToolTip();
}

void QueueListView::hideToolTip()
{
    d->toolTipItem = 0;
    d->toolTipTimer->stop();
    slotToolTip();
}

bool QueueListView::acceptToolTip(const QPoint& pos)
{
    if (columnAt(pos.x()) == 0)
        return true;

    return false;
}

void QueueListView::slotToolTip()
{
    d->toolTip->setQueueItem(d->toolTipItem);
}

void QueueListView::mouseMoveEvent(QMouseEvent* e)
{
    if (e->buttons() == Qt::NoButton)
    {
        QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(itemAt(e->pos()));

        if(d->showTips)
        {
            if (!isActiveWindow())
            {
                hideToolTip();
                return;
            }

            if (item != d->toolTipItem)
            {
                hideToolTip();

                if (acceptToolTip(e->pos()))
                {
                    d->toolTipItem = item;
                    d->toolTipTimer->setSingleShot(true);
                    d->toolTipTimer->start(500);
                }
            }

            if(item == d->toolTipItem && !acceptToolTip(e->pos()))
            {
                hideToolTip();
            }
        }

        return;
    }

    hideToolTip();
    QTreeWidget::mouseMoveEvent(e);
}

void QueueListView::wheelEvent(QWheelEvent* e)
{
    hideToolTip();
    QTreeWidget::wheelEvent(e);
}

void QueueListView::keyPressEvent(QKeyEvent* e)
{
    hideToolTip();
    QTreeWidget::keyPressEvent(e);
}

void QueueListView::focusOutEvent(QFocusEvent* e)
{
    hideToolTip();
    QTreeWidget::focusOutEvent(e);
}

void QueueListView::leaveEvent(QEvent* e)
{
    hideToolTip();
    QTreeWidget::leaveEvent(e);
}

void QueueListView::slotAddItems(const ImageInfoList& list)
{
    if ( list.count() == 0 ) return;

    for( ImageInfoList::ConstIterator it = list.begin(); it != list.end(); ++it )
    {
        ImageInfo info = *it;

        // Check if the new item already exist in the list.

        bool find               = false;
        QueueListViewItem *item = 0;

        QTreeWidgetItemIterator iter(this);
        while (*iter)
        {
            item = dynamic_cast<QueueListViewItem*>(*iter);

            if (item->info() == info)
                find = true;

            ++iter;
        }

        if (!find)
        {
            item = new QueueListViewItem(this, info);
            d->thumbLoadThread->find(info.fileUrl().path());
        }
    }
    emit signalQueueContentsChanged();
}

void QueueListView::slotThumbnailLoaded(const LoadingDescription& desc, const QPixmap& pix)
{
    QTreeWidgetItemIterator it(this);
    while (*it)
    {
        QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
        if (item->info().fileUrl() == KUrl(desc.filePath))
        {
            if (pix.isNull())
                item->setThumb(SmallIcon("image-x-generic", ICONSIZE, KIconLoader::DisabledState));
            else
                item->setThumb(pix.scaled(ICONSIZE, ICONSIZE, Qt::KeepAspectRatio));

            return;
        }
        ++it;
    }
}

void QueueListView::slotClearList()
{
    removeItems(QueueListViewPriv::ItemsAll);
    emit signalQueueContentsChanged();
}

void QueueListView::slotRemoveSelectedItems()
{
    removeItems(QueueListViewPriv::ItemsSelected);
    emit signalQueueContentsChanged();
}

void QueueListView::slotRemoveItemsDone()
{
    removeItems(QueueListViewPriv::ItemsDone);
    emit signalQueueContentsChanged();
}

void QueueListView::removeItems(int removeType)
{
    hideToolTip();

    bool find;
    do
    {
        find = false;
        QTreeWidgetItemIterator it(this);
        while (*it)
        {
            QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
            switch(removeType)
            {
                case QueueListViewPriv::ItemsSelected:
                {
                    if (item->isSelected())
                    {
                        delete item;
                        find = true;
                    }
                    break;
                }
                case QueueListViewPriv::ItemsDone:
                {
                    if (item->isDone())
                    {
                        delete item;
                        find = true;
                    }
                    break;
                }
                default:  // ItemsAll
                {
                    delete item;
                    find = true;
                    break;
                }
            }
            ++it;
        }
    }
    while(find);

    emit signalQueueContentsChanged();
}

void QueueListView::removeItemByInfo(const ImageInfo& info)
{
    hideToolTip();

    bool find;
    do
    {
        find = false;
        QTreeWidgetItemIterator it(this);
        while (*it)
        {
            QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
            if (item->info() == info)
            {
                delete item;
                find = true;
                break;
            }
            ++it;
        }
    }
    while(find);

    emit signalQueueContentsChanged();
}

bool QueueListView::findItemByInfo(const ImageInfo& info)
{
    QTreeWidgetItemIterator it(this);
    while (*it)
    {
        QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
        if (item->info() == info)
            return true;

        ++it;
    }

    return false;
}

QueueListViewItem* QueueListView::findItemByUrl(const KUrl& url)
{
    QTreeWidgetItemIterator it(this);
    while (*it)
    {
        QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
        if (item->info().fileUrl() == url)
            return item;

        ++it;
    }

    return 0;
}

int QueueListView::itemsCount()
{
    int count = 0;
    QTreeWidgetItemIterator it(this);
    while (*it)
    {
        QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
        if (item) count++;
        ++it;
    }
    return count;
}

ImageInfoList QueueListView::pendingItemsList()
{
    ImageInfoList list;
    QTreeWidgetItemIterator it(this);
    while (*it)
    {
        QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
        if (item && !item->isDone())
            list.append(item->info());
        ++it;
    }
    return list;
}

int QueueListView::pendingItemsCount()
{
    return pendingItemsList().count();
}

int QueueListView::pendingTasksCount()
{
    int count = 0;
    QTreeWidgetItemIterator it(this);
    while (*it)
    {
        QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
        if (item && !item->isDone())
            count += assignedTools().toolsMap.count();
        ++it;
    }
    return count;
}

void QueueListView::setSettings(const QueueSettings& settings)
{
    d->settings = settings;
    updateDestFileNames();
}

QueueSettings QueueListView::settings()
{
    return d->settings;
}

void QueueListView::setAssignedTools(const AssignedBatchTools& tools)
{
    d->toolsList = tools;
    updateDestFileNames();
}

AssignedBatchTools QueueListView::assignedTools()
{
    return d->toolsList;
}

void QueueListView::slotAssignedToolsChanged(const AssignedBatchTools& tools)
{
    setAssignedTools(tools);
}

void QueueListView::updateDestFileNames()
{
    int index = 1;
    QTreeWidgetItemIterator it(this);
    while (*it)
    {
        QueueListViewItem* item = dynamic_cast<QueueListViewItem*>(*it);
        if (item)
        {
            // Update base name using queue renaming rules.
            ImageInfo info = item->info();
            QFileInfo fi(info.name());

            QString baseName = fi.baseName();
            if (settings().renamingRule == QueueSettings::CUSTOMIZE)
            {
                QString parser = settings().renamingParser;
                QString camera = info.photoInfoContainer().make + info.photoInfoContainer().model;
                QDateTime date = info.dateTime();
                baseName       = ManualRenameInput::parser(parser, baseName, camera, date, index);
            }

            // Update suffix using assigned batch tool rules.
            AssignedBatchTools tools = assignedTools();
            tools.itemUrl            = item->info().fileUrl();
            QString newSuffix        = tools.targetSuffix();

            item->setDestFileName(QString("%1.%2").arg(baseName).arg(newSuffix));
        }
        ++it;
        index++;
    }
}

void QueueListView::slotContextMenu()
{
    KActionCollection *acol = QueueMgrWindow::queueManagerWindow()->actionCollection();
    KMenu popmenu(this);
    popmenu.addAction(acol->action("queuemgr_removeitemssel"));
    popmenu.addSeparator();
    popmenu.addAction(acol->action("queuemgr_clearlist"));
    popmenu.exec(QCursor::pos());
}

}  // namespace Digikam
