/* ============================================================
 *
 * This file is a part of kipi-plugins project
 * http://www.kipi-plugins.org
 *
 * Date        : 2003-10-24
 * Description : Raw converter batch dialog
 *
 * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
 * Copyright (C) 2006-2008 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.
 *
 * ============================================================ */

// C Ansi includes.

extern "C"
{
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
}

// C++ includes.

#include <cstdio>

// Qt includes.

#include <qframe.h>
#include <qlabel.h>
#include <qlayout.h>
#include <qtooltip.h>
#include <qtimer.h>
#include <qfileinfo.h>
#include <qevent.h>
#include <qpixmap.h>
#include <qpushbutton.h>
#include <qfile.h>

// KDE includes.

#include <kcursor.h>
#include <kdebug.h>
#include <klistview.h>
#include <klocale.h>
#include <kurl.h>
#include <kiconloader.h>
#include <kprogress.h>
#include <kio/renamedlg.h>
#include <kmessagebox.h>
#include <kconfig.h>
#include <kapplication.h>
#include <khelpmenu.h>
#include <kpopupmenu.h>
#include <kstandarddirs.h>

// LibKDcraw includes.

#include <libkdcraw/version.h>
#include <libkdcraw/dcrawsettingswidget.h>

// Local includes.

#include "kpaboutdata.h"
#include "pluginsversion.h"
#include "rawdecodingiface.h"
#include "savesettingswidget.h"
#include "actionthread.h"
#include "clistviewitem.h"
#include "batchdialog.h"
#include "batchdialog.moc"

namespace KIPIRawConverterPlugin
{

BatchDialog::BatchDialog(QWidget* /*parent*/)
           : KDialogBase(0, 0, false, i18n("Raw Images Batch Converter"),
                         Help|Default|User1|User2|Close, Close, true,
                         i18n("Con&vert"), i18n("&Abort"))
{
    m_currentConvertItem = 0;
    m_thread             = 0;
    m_page = new QWidget( this );
    setMainWidget( m_page );
    QGridLayout *mainLayout = new QGridLayout(m_page, 2, 1, 0, spacingHint());

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

    m_listView = new KListView(m_page);
    m_listView->addColumn( i18n("Thumbnail") );
    m_listView->addColumn( i18n("Raw File") );
    m_listView->addColumn( i18n("Target File") );
    m_listView->addColumn( i18n("Camera") );
    m_listView->setResizeMode(QListView::AllColumns);
    m_listView->setAllColumnsShowFocus(true);
    m_listView->setSorting(-1);
    m_listView->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    m_listView->setSelectionMode(QListView::Single);
    m_listView->setMinimumWidth(450);

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

    m_decodingSettingsBox = new KDcrawIface::DcrawSettingsWidget(m_page, false, true, true);
    m_saveSettingsBox     = new SaveSettingsWidget(m_page);

#if KDCRAW_VERSION >= 0x000105
    m_decodingSettingsBox->addItem(m_saveSettingsBox, i18n("Save settings"));
    m_decodingSettingsBox->updateMinimumWidth();
#else
    m_decodingSettingsBox->insertTab(m_saveSettingsBox, i18n("Save settings"));
#endif

    m_progressBar = new KProgress(m_page);
    m_progressBar->setMaximumHeight( fontMetrics().height()+2 );
    m_progressBar->hide();

    mainLayout->addMultiCellWidget(m_listView,            0, 2, 0, 0);
    mainLayout->addMultiCellWidget(m_decodingSettingsBox, 0, 0, 1, 1);
    mainLayout->addMultiCellWidget(m_progressBar,         1, 1, 1, 1);
    mainLayout->setColStretch(0, 10);
    mainLayout->setRowStretch(2, 10);

    // ---------------------------------------------------------------
    // About data and help button.

    m_about = new KIPIPlugins::KPAboutData(I18N_NOOP("RAW Image Converter"),
                                           0,
                                           KAboutData::License_GPL,
                                           I18N_NOOP("A Kipi plugin to batch convert Raw images"),
                                           "(c) 2003-2005, Renchi Raju\n"
                                           "(c) 2006-2008, Gilles Caulier");

    m_about->addAuthor("Renchi Raju", I18N_NOOP("Original author"),
                       "renchi at pooh dot tam dot uiuc dot edu");

    m_about->addAuthor("Gilles Caulier", I18N_NOOP("Maintainer"),
                       "caulier dot gilles at gmail dot com");

    KHelpMenu* helpMenu = new KHelpMenu(this, m_about, false);
    helpMenu->menu()->removeItemAt(0);
    helpMenu->menu()->insertItem(i18n("Plugin Handbook"),
                                 this, SLOT(slotHelp()), 0, -1, 0);
    actionButton(Help)->setPopup( helpMenu->menu() );

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

    setButtonTip( User1, i18n("<p>Start converting the Raw images from current settings"));
    setButtonTip( User2, i18n("<p>Abort the current Raw files conversion"));
    setButtonTip( Close, i18n("<p>Exit Raw Converter"));

    m_blinkConvertTimer = new QTimer(this);
    m_thread            = new ActionThread(this);

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

    connect(m_blinkConvertTimer, SIGNAL(timeout()),
            this, SLOT(slotConvertBlinkTimerDone()));

    connect(m_saveSettingsBox, SIGNAL(signalSaveFormatChanged()),
            this, SLOT(slotSaveFormatChanged()));

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

    m_itemDict.setAutoDelete(true);
    busy(false);
    readSettings();
}

BatchDialog::~BatchDialog()
{
    delete m_about;
    delete m_thread;
}

void BatchDialog::closeEvent(QCloseEvent *e)
{
    if (!e) return;
    m_blinkConvertTimer->stop();
    m_thread->cancel();
    saveSettings();
    e->accept();
}

void BatchDialog::slotClose()
{
    m_blinkConvertTimer->stop();
    m_thread->cancel();
    saveSettings();
    KDialogBase::slotClose();
}

void BatchDialog::slotDefault()
{
    m_decodingSettingsBox->setDefaultSettings();
    m_saveSettingsBox->setDefaultSettings();
}

void BatchDialog::readSettings()
{
    KConfig config("kipirc");
    config.setGroup("RawConverter Settings");

    m_decodingSettingsBox->setWhiteBalance((KDcrawIface::RawDecodingSettings::WhiteBalance)
                                            config.readNumEntry("White Balance",
                                            KDcrawIface::RawDecodingSettings::CAMERA));
    m_decodingSettingsBox->setCustomWhiteBalance(config.readNumEntry("Custom White Balance", 6500));
    m_decodingSettingsBox->setCustomWhiteBalanceGreen(config.readDoubleNumEntry("Custom White Balance Green", 1.0));
    m_decodingSettingsBox->setFourColor(config.readBoolEntry("Four Color RGB", false));
    m_decodingSettingsBox->setUnclipColor(config.readNumEntry("Unclip Color", 0));
    m_decodingSettingsBox->setDontStretchPixels(config.readBoolEntry("Dont Stretch Pixels", false));
    m_decodingSettingsBox->setNoiseReduction(config.readBoolEntry("Use Noise Reduction", false));
    m_decodingSettingsBox->setBrightness(config.readDoubleNumEntry("Brightness Multiplier", 1.0));
    m_decodingSettingsBox->setUseBlackPoint(config.readBoolEntry("Use Black Point", false));
    m_decodingSettingsBox->setBlackPoint(config.readNumEntry("Black Point", 0));
#if KDCRAW_VERSION >= 0x000105
    m_decodingSettingsBox->setUseWhitePoint(config.readBoolEntry("Use White Point", false));
    m_decodingSettingsBox->setWhitePoint(config.readNumEntry("White Point", 0));
    m_decodingSettingsBox->setMedianFilterPasses(config.readNumEntry("Median Filter Passes", 0));
#endif
    m_decodingSettingsBox->setNRThreshold(config.readNumEntry("NR Threshold", 100));
    m_decodingSettingsBox->setUseCACorrection(config.readBoolEntry("EnableCACorrection", false));
    m_decodingSettingsBox->setcaRedMultiplier(config.readDoubleNumEntry("caRedMultiplier", 1.0));
    m_decodingSettingsBox->setcaBlueMultiplier(config.readDoubleNumEntry("caBlueMultiplier", 1.0));

    m_decodingSettingsBox->setQuality(
        (KDcrawIface::RawDecodingSettings::DecodingQuality)config.readNumEntry("Decoding Quality", 
            (int)(KDcrawIface::RawDecodingSettings::BILINEAR))); 

    m_decodingSettingsBox->setOutputColorSpace(
        (KDcrawIface::RawDecodingSettings::OutputColorSpace)config.readNumEntry("Output Color Space", 
            (int)(KDcrawIface::RawDecodingSettings::SRGB))); 

    m_saveSettingsBox->setFileFormat(
        (SaveSettingsWidget::OutputFormat)config.readNumEntry("Output Format", 
            (int)(SaveSettingsWidget::OUTPUT_PNG))); 

    m_saveSettingsBox->setConflictRule(
        (SaveSettingsWidget::ConflictRule)config.readNumEntry("Conflict",
            (int)(SaveSettingsWidget::OVERWRITE)));

    resize(configDialogSize(config, QString("Batch Raw Converter Dialog")));
}

void BatchDialog::saveSettings()
{
    KConfig config("kipirc");
    config.setGroup("RawConverter Settings");

    config.writeEntry("White Balance", m_decodingSettingsBox->whiteBalance());
    config.writeEntry("Custom White Balance", m_decodingSettingsBox->customWhiteBalance());
    config.writeEntry("Custom White Balance Green", m_decodingSettingsBox->customWhiteBalanceGreen());
    config.writeEntry("Four Color RGB", m_decodingSettingsBox->useFourColor());
    config.writeEntry("Unclip Color", m_decodingSettingsBox->unclipColor());
    config.writeEntry("Dont Stretch Pixels", m_decodingSettingsBox->useDontStretchPixels());
    config.writeEntry("Use Noise Reduction", m_decodingSettingsBox->useNoiseReduction());
    config.writeEntry("Brightness Multiplier", m_decodingSettingsBox->brightness());
    config.writeEntry("Use Black Point", m_decodingSettingsBox->useBlackPoint());
    config.writeEntry("Black Point", m_decodingSettingsBox->blackPoint());
#if KDCRAW_VERSION >= 0x000105
    config.writeEntry("Use White Point", m_decodingSettingsBox->useWhitePoint());
    config.writeEntry("White Point", m_decodingSettingsBox->whitePoint());
    config.writeEntry("Median Filter Passes", m_decodingSettingsBox->medianFilterPasses());
#endif
    config.writeEntry("NR Threshold", m_decodingSettingsBox->NRThreshold());
    config.writeEntry("EnableCACorrection", m_decodingSettingsBox->useCACorrection());
    config.writeEntry("caRedMultiplier", m_decodingSettingsBox->caRedMultiplier());
    config.writeEntry("caBlueMultiplier", m_decodingSettingsBox->caBlueMultiplier());
    config.writeEntry("Decoding Quality", (int)m_decodingSettingsBox->quality());
    config.writeEntry("Output Color Space", (int)m_decodingSettingsBox->outputColorSpace());

    config.writeEntry("Output Format", (int)m_saveSettingsBox->fileFormat());
    config.writeEntry("Conflict", (int)m_saveSettingsBox->conflictRule());

    saveDialogSize(config, QString("Batch Raw Converter Dialog"));
    config.sync();
}

void BatchDialog::slotHelp()
{
    KApplication::kApplication()->invokeHelp("rawconverter", "kipi-plugins");
}

void BatchDialog::slotUser1()
{
    m_fileList.clear();

    QListViewItemIterator it( m_listView );
    while ( it.current() ) 
    {
        CListViewItem *item = (CListViewItem*) it.current();
        if (item->isEnabled())
        {
            item->setPixmap(1, 0);
            m_fileList.append(item->rawItem->directory + QString("/") + item->rawItem->src);
        }
        ++it;
    }

    if (m_fileList.empty()) 
    {
        KMessageBox::error(this, i18n("There is no Raw file to process in the list!"));
        busy(false);
        slotAborted();
        return;
    }

    m_progressBar->setTotalSteps(m_fileList.count());
    m_progressBar->setProgress(0);
    m_progressBar->show();

    KDcrawIface::RawDecodingSettings rawDecodingSettings;
    rawDecodingSettings.whiteBalance               = m_decodingSettingsBox->whiteBalance();
    rawDecodingSettings.customWhiteBalance         = m_decodingSettingsBox->customWhiteBalance();
    rawDecodingSettings.customWhiteBalanceGreen    = m_decodingSettingsBox->customWhiteBalanceGreen();
    rawDecodingSettings.RGBInterpolate4Colors      = m_decodingSettingsBox->useFourColor();
    rawDecodingSettings.unclipColors               = m_decodingSettingsBox->unclipColor();
    rawDecodingSettings.DontStretchPixels          = m_decodingSettingsBox->useDontStretchPixels();
    rawDecodingSettings.enableNoiseReduction       = m_decodingSettingsBox->useNoiseReduction();
    rawDecodingSettings.brightness                 = m_decodingSettingsBox->brightness();
    rawDecodingSettings.enableBlackPoint           = m_decodingSettingsBox->useBlackPoint();
    rawDecodingSettings.blackPoint                 = m_decodingSettingsBox->blackPoint();
#if KDCRAW_VERSION >= 0x000105
    rawDecodingSettings.enableWhitePoint           = m_decodingSettingsBox->useWhitePoint();
    rawDecodingSettings.whitePoint                 = m_decodingSettingsBox->whitePoint();
    rawDecodingSettings.medianFilterPasses         = m_decodingSettingsBox->medianFilterPasses();
#endif
    rawDecodingSettings.NRThreshold                = m_decodingSettingsBox->NRThreshold();
    rawDecodingSettings.enableCACorrection         = m_decodingSettingsBox->useCACorrection();
    rawDecodingSettings.caMultiplier[0]            = m_decodingSettingsBox->caRedMultiplier();
    rawDecodingSettings.caMultiplier[1]            = m_decodingSettingsBox->caBlueMultiplier();
    rawDecodingSettings.RAWQuality                 = m_decodingSettingsBox->quality();
    rawDecodingSettings.outputColorSpace           = m_decodingSettingsBox->outputColorSpace();

    m_thread->setRawDecodingSettings(rawDecodingSettings, m_saveSettingsBox->fileFormat());
    processOne();
}

void BatchDialog::slotUser2()
{
    m_blinkConvertTimer->stop();
    m_fileList.clear();
    m_thread->cancel();
    busy(false);

    if (m_currentConvertItem)
        m_currentConvertItem->viewItem->setPixmap(1, SmallIcon("cancel"));

    QTimer::singleShot(500, this, SLOT(slotAborted()));
}

void BatchDialog::slotAborted()
{
    m_progressBar->setProgress(0);
    m_progressBar->hide();
}

void BatchDialog::addItems(const QStringList& itemList)
{
    QString ext;

    switch(m_saveSettingsBox->fileFormat())
    {
        case SaveSettingsWidget::OUTPUT_JPEG:
            ext = "jpg";
            break;
        case SaveSettingsWidget::OUTPUT_TIFF:
            ext = "tif";
            break;
        case SaveSettingsWidget::OUTPUT_PPM:
            ext = "ppm";
            break;
        case SaveSettingsWidget::OUTPUT_PNG:
            ext = "png";
            break;
    }

    KURL::List urlList;

    QPixmap pix(SmallIcon( "file_broken", KIcon::SizeLarge, KIcon::DisabledState ));

    for (QStringList::const_iterator  it = itemList.begin();
         it != itemList.end(); ++it) 
    {
        QFileInfo fi(*it);
        if (fi.exists() && !m_itemDict.find(fi.fileName())) 
        {
            RawItem *item = new RawItem;
            item->directory = fi.dirPath();
            item->src  = fi.fileName();
            item->dest = fi.baseName() + QString(".") + ext;
            new CListViewItem(m_listView, pix, item, m_listView->lastItem());
            m_itemDict.insert(item->src, item);
            urlList.append(fi.absFilePath());
        }
    }

    if (!urlList.empty()) 
    {
        m_thread->identifyRawFiles(urlList);
        if (!m_thread->running())
            m_thread->start();
    }
}

void BatchDialog::slotSaveFormatChanged()
{
    QString ext;

    switch(m_saveSettingsBox->fileFormat())
    {
        case SaveSettingsWidget::OUTPUT_JPEG:
            ext = "jpg";
            break;
        case SaveSettingsWidget::OUTPUT_TIFF:
            ext = "tif";
            break;
        case SaveSettingsWidget::OUTPUT_PPM:
            ext = "ppm";
            break;
        case SaveSettingsWidget::OUTPUT_PNG:
            ext = "png";
            break;
    }

    QListViewItemIterator it( m_listView );
    while ( it.current() ) 
    {
        CListViewItem *item = (CListViewItem*) it.current();
        if (item->isEnabled())
        {
            RawItem *rawItem = item->rawItem;
            QFileInfo fi(rawItem->directory + QString("/") + rawItem->src);
            rawItem->dest = fi.baseName() + QString(".") + ext;
            item->setText(2, rawItem->dest);
        }
        ++it;
    }
}

void BatchDialog::processOne()
{
    if (m_fileList.empty()) 
    {
        busy(false);
        slotAborted();
        return;
    }

    QString file(m_fileList.first());
    m_fileList.pop_front();

    m_thread->processRawFile(KURL(file));
    if (!m_thread->running())
        m_thread->start();
}

void BatchDialog::busy(bool busy)
{
    enableButton(User1, !busy);
    enableButton(User2, busy);
    enableButton(Close, !busy);

    m_decodingSettingsBox->setEnabled(!busy);
    m_saveSettingsBox->setEnabled(!busy);
    m_listView->setEnabled(!busy);

    busy ? m_page->setCursor(KCursor::waitCursor()) : m_page->unsetCursor();
}

void BatchDialog::slotConvertBlinkTimerDone()
{
    if(m_convertBlink)
    {
        if (m_currentConvertItem)
            m_currentConvertItem->viewItem->setPixmap(1, SmallIcon("1rightarrow"));
    }
    else
    {
        if (m_currentConvertItem)
            m_currentConvertItem->viewItem->setPixmap(1, SmallIcon("2rightarrow"));
    }

    m_convertBlink = !m_convertBlink;
    m_blinkConvertTimer->start(500);
}

void BatchDialog::processing(const QString& file)
{
    QString filename     = QFileInfo(file).fileName();
    m_currentConvertItem = m_itemDict.find(filename);
    if (m_currentConvertItem) 
    {
        m_listView->setSelected(m_currentConvertItem->viewItem, true);
        m_listView->ensureItemVisible(m_currentConvertItem->viewItem);
    }

    m_convertBlink = false;
    m_blinkConvertTimer->start(500);
}

void BatchDialog::processed(const QString& file, const QString& tmpFile)
{
    m_blinkConvertTimer->stop();
    QString filename = QFileInfo(file).fileName();
    QString destFile(m_currentConvertItem->directory + QString("/") + m_currentConvertItem->dest);

    if (m_saveSettingsBox->conflictRule() != SaveSettingsWidget::OVERWRITE)
    {
        struct stat statBuf;
        if (::stat(QFile::encodeName(destFile), &statBuf) == 0) 
        {
            KIO::RenameDlg dlg(this, i18n("Save Raw Image converted from '%1' as")
                                     .arg(m_currentConvertItem->src),
                               tmpFile, destFile,
                               KIO::RenameDlg_Mode(KIO::M_SINGLE | KIO::M_OVERWRITE | KIO::M_SKIP));

            switch (dlg.exec())
            {
                case KIO::R_CANCEL:
                case KIO::R_SKIP:
                {
                    destFile = QString();
                    m_currentConvertItem->viewItem->setPixmap(1, SmallIcon("cancel"));
                    break;
                }
                case KIO::R_RENAME:
                {
                    destFile = dlg.newDestURL().path();
                    break;
                }
                default:    // Overwrite.
                    break;
            }
        }
    }

    if (!destFile.isEmpty()) 
    {
        if (::rename(QFile::encodeName(tmpFile), QFile::encodeName(destFile)) != 0)
        {
            KMessageBox::error(this, i18n("Failed to save image %1").arg( destFile ));
            m_currentConvertItem->viewItem->setPixmap(1, SmallIcon("cancel"));
        }
        else 
        {
            m_currentConvertItem->dest = QFileInfo(destFile).fileName();
            m_currentConvertItem->viewItem->setText(2, m_currentConvertItem->dest);
            m_currentConvertItem->viewItem->setPixmap(1, SmallIcon("ok"));
        }
    }

    m_progressBar->advance(1);
    m_currentConvertItem = 0;
}

void BatchDialog::processingFailed(const QString& file)
{
    QString filename = QFileInfo(file).fileName();
    m_currentConvertItem->viewItem->setPixmap(1, SmallIcon("no"));
    m_progressBar->advance(1);
    m_currentConvertItem = 0;
}

void BatchDialog::customEvent(QCustomEvent *event)
{
    if (!event) return;

    EventData *d = (EventData*) event->data();
    if (!d) return;

    QString text;

    if (d->starting)            // Something have been started...
    {
        switch (d->action) 
        {
            case(IDENTIFY): 
                break;
            case(PROCESS):
            {
                busy(true);
                processing(d->filePath);
                break;
            }
            default: 
            {
                kdWarning( 51000 ) << "KIPIRawConverterPlugin: Unknown event" << endl;
                break;
            }
        }
    }
    else
    {
        if (!d->success)        // Something is failed...
        {
            switch (d->action) 
            {
                case(IDENTIFY): 
                    break;
                case(PROCESS):
                {
                    processingFailed(d->filePath);
                    processOne();
                    break;
                }
                default: 
                {
                    kdWarning( 51000 ) << "KIPIRawConverterPlugin: Unknown event" << endl;
                    break;
                }
            }
        }
        else                    // Something is done...
        {
            switch (d->action)
            {
                case(IDENTIFY): 
                {
                    QFileInfo fi(d->filePath);
                    RawItem *rawItem = m_itemDict.find(fi.fileName());
                    if (rawItem) 
                    {
                        if (!d->image.isNull())
                        {
                            QPixmap pix = QPixmap(d->image.scale(64, 64, QImage::ScaleMin));
                            rawItem->viewItem->setThumbnail(pix);
                        }
                        rawItem->viewItem->setText(3, d->message);
                        rawItem->identity = d->message;
                    }
                    break;
                }
                case(PROCESS):
                {
                    processed(d->filePath, d->destPath);
                    processOne();
                    break;
                }
                default: 
                {
                    kdWarning( 51000 ) << "KIPIRawConverterPlugin: Unknown event" << endl;
                    break;
                }
            }
        }
    }

    delete d;
}

} // NameSpace KIPIRawConverterPlugin
