/* ============================================================
 *
 * This file is a part of kipi-plugins project
 * http://www.kipi-plugins.org
 *
 * Date        : 2005-12-19
 * Description : a plugin to export image collections using SimpleViewer.
 *
 * Copyright (C) 2005-2006 by Joern Ahrens <joern dot ahrens at kdemail dot net>
 * Copyright (C) 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.
 * 
 * ============================================================ */

// QT includes

#include <qtimer.h>
#include <qimage.h>
#include <qfile.h>
#include <qdir.h>
#include <qcstring.h>
#include <qdatastream.h>

// KDE includes

#include <kdebug.h>
#include <klocale.h>
#include <kapplication.h>
#include <kio/netaccess.h>
#include <kfilemetainfo.h>
#include <kstandarddirs.h>
#include <kio/job.h>
#include <kzip.h>
#include <kmessagebox.h>
#include <kaboutdata.h>
#include <ktempdir.h>

// KIPI includes

#include <libkipi/version.h>
#include <libkipi/batchprogressdialog.h>
#include <libkipi/imageinfo.h>

// LibKDcraw includes.

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

#if KDCRAW_VERSION < 0x000106
#include <libkdcraw/dcrawbinary.h>
#endif

// LibKExiv2 includes.

#include <libkexiv2/kexiv2.h>

// Local includes

#include "pluginsversion.h"
#include "firstrundlg.h"
#include "svedialog.h"
#include "simpleviewerexport.h"
#include "simpleviewerexport.moc"

namespace KIPISimpleViewerExportPlugin
{

// maxium size of a simpleviewer thumbnail
// TODO: read from configfile
const int maxThumbSize = 45;
const QString viewer("viewer.swf");

void SimpleViewerExport::run(KIPI::Interface* interface, QObject *parent)
{
    SimpleViewerExport *plugin = new SimpleViewerExport(interface, parent);

    if(!plugin->checkSimpleViewer())
    {
        if(!plugin->installSimpleViewer())
        {
            KMessageBox::error(kapp->activeWindow(), i18n("SimpleViewer installation failed"));
            return;
        }
    }

    if(plugin->configure())
        plugin->startExport();

    delete plugin;
}

SimpleViewerExport::SimpleViewerExport(KIPI::Interface* interface, QObject *parent)
                  : QObject(parent)
{
    m_interface    = interface;
    m_configDlg    = 0;
    m_totalActions = 0;
    m_action       = 0;
    m_canceled     = true;
    m_dataLocal    = locateLocal("data", "kipiplugin_simpleviewerexport/simpleviewer/", true);
    m_tempDir      = 0;

    m_simpleViewerFiles.append(viewer);
    m_simpleViewerFiles.append("swfobject.js");

    const KAboutData *data = KApplication::kApplication()->aboutData();
    m_hostName             = QString::QString( data->appName() );
    m_hostURL              = data->homepage();

    if (m_hostURL.isEmpty())
    {
        m_hostName = "Kipi";
        m_hostURL  = "http://www.kipi-plugins.org";
    }
}

SimpleViewerExport::~SimpleViewerExport()
{
    if(m_tempDir)
        delete m_tempDir;
}

bool SimpleViewerExport::configure()
{
    m_canceled = false;

    if(!m_configDlg)
        m_configDlg = new SVEDialog(m_interface, kapp->activeWindow());

    bool configured = false;
    while(!configured)
    {
        if(m_configDlg->exec() == QDialog::Rejected)
            return false;

        configured = true;

        if(KIO::NetAccess::exists(m_configDlg->exportURL(), false, kapp->activeWindow()))
        {
            int ret = KMessageBox::warningYesNoCancel(kapp->activeWindow(),
                                                      i18n("Target folder %1 already exists.\n"
                                                           "Do you want to overwrite it (all data in this folder will be lost)")
                                                           .arg(m_configDlg->exportURL()));

            switch(ret)
            {
                case KMessageBox::Yes:
                    if(!KIO::NetAccess::del(m_configDlg->exportURL(), kapp->activeWindow()))
                    {
                        KMessageBox::error(kapp->activeWindow(), i18n("Could not delete %1\n"
                                "Please choose another export folder").arg(m_configDlg->exportURL()));
                        configured = false;
                    }
                    break;

                case KMessageBox::No:
                    configured = false;
                    break;

                case KMessageBox::Cancel:
                    return false;
                    break;
            };
        }
    }

    return true;
}

void SimpleViewerExport::startExport()
{
    if(m_canceled)
        return;

    m_progressDlg = new KIPI::BatchProgressDialog(kapp->activeWindow(), i18n("Flash Export"));

    connect(m_progressDlg, SIGNAL(cancelClicked()),
            this, SLOT(slotCancel()));

    m_progressDlg->show();
    kapp->processEvents();

    // Estimate the number of actions for the KIPI progress dialog.
    m_progressDlg->addedAction(i18n("Estimate the number of actions to do..."), KIPI::StartingMessage);
    m_albumsList = m_configDlg->getSelectedAlbums();
    m_totalActions = 0;
    for( QValueList<KIPI::ImageCollection>::Iterator it = m_albumsList.begin() ;
         !m_canceled && (it != m_albumsList.end()) ; ++it )
    {
        m_totalActions += (*it).images().count();
    }

    // +copying SimpleViewer, +creating index.html
    m_totalActions += 2;

    m_progressDlg->setProgress(0, m_totalActions);

    slotProcess();

#if KDE_VERSION >= 0x30200
        m_progressDlg->setButtonCancel(KStdGuiItem::close());
#else
        m_progressDlg->setButtonCancelText(i18n("&Close"));
#endif

}

void SimpleViewerExport::slotCancel()
{
    m_progressDlg->addedAction(i18n("Export canceled"), KIPI::ErrorMessage);
    m_canceled = true;
}

void SimpleViewerExport::slotProcess()
{
    if(m_canceled)
        return;

    m_progressDlg->addedAction(i18n("Initialising..."), KIPI::StartingMessage);

    if(!m_canceled && !createExportDirectories())
    {
            m_progressDlg->addedAction(i18n("Failed to create export directories"),
                                       KIPI::ErrorMessage);
        return;
    }

    if(!m_canceled && !exportImages())
    {
        m_progressDlg->addedAction(i18n("Failed to export the images"),
                                   KIPI::ErrorMessage);
        return;
    }

    if(!m_canceled && !createIndex())
    {
        m_progressDlg->addedAction(i18n("Failed to create index.html"),
                                   KIPI::ErrorMessage);
        return;
    }

    if(!m_canceled && !copySimpleViewer())
    {
        m_progressDlg->addedAction(i18n("Failed to copy SimpleViewer files"),
                                   KIPI::ErrorMessage);
        return;
    }

    if(!m_canceled && !upload())
    {
        m_progressDlg->addedAction(i18n("Failed to upload the gallery"),
                                   KIPI::ErrorMessage);
        return;
    }

    if(m_canceled)
    {
        int ret = KMessageBox::warningYesNo(kapp->activeWindow(),
                                            i18n("Export was canceled.\n"
                                            "Do you want to delete the yet created files in %1 ?")
                                            .arg(m_configDlg->exportURL()));
        if(ret == KMessageBox::Yes)
        {
            KIO::NetAccess::del(m_configDlg->exportURL(), kapp->activeWindow());
        }
    }

    if(!m_canceled)
        m_progressDlg->addedAction(i18n("Finished..."), KIPI::SuccessMessage);
}

bool SimpleViewerExport::createExportDirectories()
{
    m_tempDir = new KTempDir(locateLocal("tmp", "simpleviewerexport"));
    m_tempDir->setAutoDelete(true);

    m_progressDlg->addedAction(i18n("Creating directories..."), KIPI::StartingMessage);

    KURL root = m_configDlg->exportURL();
    if(!KIO::NetAccess::mkdir(root, kapp->activeWindow()))
    {
        m_progressDlg->addedAction(i18n("Could not create folder '%1'").arg(root.url()),
                                   KIPI::ErrorMessage);
        return(false);
    }

    KURL thumbsDir = m_tempDir->name();
    thumbsDir.addPath("/thumbs");
    if(!KIO::NetAccess::mkdir(thumbsDir, kapp->activeWindow()))
    {
        m_progressDlg->addedAction(i18n("Could not create folder '%1'").arg(thumbsDir.url()),
                                   KIPI::ErrorMessage);
        return(false);
    }

    KURL imagesDir = m_tempDir->name();
    imagesDir.addPath("/images");
    if(!KIO::NetAccess::mkdir(imagesDir, kapp->activeWindow()))
    {
        m_progressDlg->addedAction(i18n("Could not create folder '%1'").arg(imagesDir.url()),
                                   KIPI::ErrorMessage);
        return(false);
    }

    m_progressDlg->setProgress(++m_action, m_totalActions);
    m_progressDlg->addedAction(i18n("Directories created..."), KIPI::SuccessMessage);

    return true;
}

bool SimpleViewerExport::exportImages()
{
    if(m_canceled)
        return false;

    m_progressDlg->addedAction(i18n("Creating images and thumbnails..."), KIPI::StartingMessage);

    KURL thumbsDir(m_tempDir->name());
    thumbsDir.addPath("/thumbs");

    KURL imagesDir(m_tempDir->name());
    imagesDir.addPath("/images");

    KURL xmlFile(m_tempDir->name());
    xmlFile.addPath("/gallery.xml");
    QFile file(xmlFile.path());
    file.open(IO_WriteOnly);

    QDomDocument xmlDoc;
    xmlDoc.appendChild(xmlDoc.createProcessingInstruction( QString::fromLatin1("xml"),
                       QString::fromLatin1("version=\"1.0\" encoding=\"UTF-8\"") ) );
    QDomElement galleryElem = xmlDoc.createElement(QString::fromLatin1("simpleviewerGallery")); 
    xmlDoc.appendChild( galleryElem );
    galleryElem.setAttribute(QString::fromLatin1("maxImageWidth"),    m_configDlg->maxImageDimension());
    galleryElem.setAttribute(QString::fromLatin1("maxImageHeight"),   m_configDlg->maxImageDimension());
    galleryElem.setAttribute(QString::fromLatin1("textColor"),        m_configDlg->textColor().name().replace("#", "0x"));
    galleryElem.setAttribute(QString::fromLatin1("frameColor"),       m_configDlg->frameColor().name().replace("#", "0x"));
    galleryElem.setAttribute(QString::fromLatin1("bgColor"),          m_configDlg->backgroundColor().name().replace("#", "0x"));
    galleryElem.setAttribute(QString::fromLatin1("frameWidth"),       m_configDlg->frameWidth());
    galleryElem.setAttribute(QString::fromLatin1("stagePadding"),     m_configDlg->stagePadding());
    galleryElem.setAttribute(QString::fromLatin1("thumbnailColumns"), m_configDlg->thumbnailColumns());
    galleryElem.setAttribute(QString::fromLatin1("thumbnailRows"),    m_configDlg->thumbnailRows());
    galleryElem.setAttribute(QString::fromLatin1("navPosition"),      m_configDlg->navPosition());
    galleryElem.setAttribute(QString::fromLatin1("navDirection"),     m_configDlg->navDirection());
    galleryElem.setAttribute(QString::fromLatin1("title"),            m_configDlg->title());
    galleryElem.setAttribute(QString::fromLatin1("imagePath"),        QString());
    galleryElem.setAttribute(QString::fromLatin1("thumbPath"),        QString());

    KExiv2Iface::KExiv2 meta;
    QImage              image;
    QImage              thumbnail;
    QString             tmp;
    QString             newName;

    int index         = 1;
    int maxSize       = m_configDlg->imagesExportSize();
    bool resizeImages = m_configDlg->resizeExportImages();

    for( QValueList<KIPI::ImageCollection>::Iterator it = m_albumsList.begin() ;
         !m_canceled && (it != m_albumsList.end()) ; ++it )
    {
        KURL::List images = (*it).images();

        for(KURL::List::Iterator it = images.begin();
            !m_canceled && (it != images.end()) ; ++it)
        {
            kapp->processEvents();
            KURL url = *it;
            QFileInfo fileInfo(url.path());

            m_progressDlg->addedAction(i18n("Processing %1").arg(url.filename()), KIPI::StartingMessage);

            // Check if RAW file.
#if KDCRAW_VERSION < 0x000106
            QString rawFilesExt(KDcrawIface::DcrawBinary::instance()->rawFiles());
#else
            QString rawFilesExt(KDcrawIface::KDcraw::rawFiles());
#endif
            if (rawFilesExt.upper().contains( fileInfo.extension(false).upper() ))
                KDcrawIface::KDcraw::loadDcrawPreview(image, url.path());
            else
                image.load(url.path());

            if(image.isNull())
            {
                m_progressDlg->addedAction(i18n("Could not open image '%1'").arg(url.filename()),
                                           KIPI::WarningMessage);
                continue;
            }

            if(!createThumbnail(image, thumbnail))
            {
                m_progressDlg->addedAction(i18n("Could not create thumbnail from '%1'").arg(url.filename()),
                                           KIPI::WarningMessage);
                continue;
            }

            if(resizeImages && !resizeImage(image, maxSize, image))
            {
                m_progressDlg->addedAction(i18n("Could not resize image '%1'").arg(url.filename()),
                                           KIPI::WarningMessage);
                continue;
            }

            meta.load(url.path());
            newName = QString("%1.%2").arg(tmp.sprintf("%03i", index)).arg(QString("jpg"));

            KURL thumbnailPath(thumbsDir);
            thumbnailPath.addPath(newName);
            thumbnail.save(thumbnailPath.path(), "JPEG");

            KURL imagePath(imagesDir);
            imagePath.addPath(newName);
            image.save(imagePath.path(), "JPEG");

            // Backup metadata from original image.
            meta.setImageProgramId(QString("Kipi-plugins"), QString(kipiplugins_version));
            meta.setImageDimensions(image.size());
            meta.save(imagePath.path());

            cfgAddImage(xmlDoc, galleryElem, url, newName);
            m_progressDlg->setProgress(++m_action, m_totalActions);
            index++;
        }
    }

    QCString data(xmlDoc.toCString());
    QDataStream stream( &file );
    stream.writeRawBytes(data.data(), data.size());
    file.close();

    m_progressDlg->addedAction(i18n("Images and thumbnails created..."), KIPI::SuccessMessage);

    return true;
}

bool SimpleViewerExport::createThumbnail(const QImage &image, QImage &thumbnail)
{
    int w = image.width();
    int h = image.height();

    int maxSize;

    if(w > maxThumbSize || h > maxThumbSize)
    {
        if(w > h)
        {
            maxSize = (int)(double)(w * maxThumbSize) / h;
        }
        else
        {
            maxSize = (int)(double)(h * maxThumbSize) / w;
        }
    }

    maxSize = (maxSize < maxThumbSize) ? maxThumbSize : maxSize;

    return resizeImage(image, maxSize, thumbnail);
}

bool SimpleViewerExport::resizeImage(const QImage &image, int maxSize, QImage &resizedImage)
{
    int w = image.width();
    int h = image.height();

    if(w > maxSize || h > maxSize)
    {
        if(w > h)
        {
            h = (int)(double)(h * maxSize) / w;
            h = (h == 0) ? 1 : h;
            w = maxSize;
        }
        else
        {
            w = (int)(double)(w * maxSize) / h;
            w = (w == 0) ? 1 : w;
            h = maxSize;
        }
        resizedImage = image.smoothScale(w, h);
    }

    return true;
}

void SimpleViewerExport::cfgAddImage(QDomDocument &xmlDoc, QDomElement &galleryElem, 
                                     const KURL &url, const QString& newName)
{
    if(m_canceled)
        return;

    QString comment;

    if(m_configDlg->showExifComments())
    {
        KIPI::ImageInfo info = m_interface->info(url);
        comment = info.description();
    }
    else
    {
        comment = QString();
    }

    QDomElement img = xmlDoc.createElement(QString::fromLatin1("image")); 
    galleryElem.appendChild(img);

    QDomElement name = xmlDoc.createElement(QString::fromLatin1("name")); 
    img.appendChild(name);
    QDomText nametxt = xmlDoc.createTextNode(newName);
    name.appendChild(nametxt);

    QDomElement caption = xmlDoc.createElement(QString::fromLatin1("caption")); 
    img.appendChild(caption);
    QDomText captiontxt = xmlDoc.createTextNode(comment);
    caption.appendChild(captiontxt);
}

bool SimpleViewerExport::createIndex()
{
    if(m_canceled)
        return false;

    m_progressDlg->addedAction(i18n("Creating index.html..."), KIPI::StartingMessage);

    QString indexTemplateName = locate("data", "kipiplugin_simpleviewerexport/index.template");
    if(indexTemplateName.isEmpty())
    {
        //TODO: errormsg
        kdDebug() << "No indexTemplateName" << endl;
        return false;
    }

    QFile infile(indexTemplateName);
    infile.open(IO_ReadOnly);
    QTextStream in(&infile);
    QString indexTemplate = in.read();
    infile.close();

    indexTemplate.replace("{TITLE}", m_configDlg->title());
    indexTemplate.replace("{COLOR}", m_configDlg->textColor().name());
    indexTemplate.replace("{BGCOLOR}", m_configDlg->backgroundColor().name());
    indexTemplate.replace("{HOSTURL}", m_hostURL);
    indexTemplate.replace("{HOSTNAME}", m_hostName);

    QFile outfile(m_tempDir->name() + "/index.html");
    outfile.open(IO_WriteOnly);
    QTextStream out(&outfile);
    out << indexTemplate;
    outfile.close();

    m_progressDlg->setProgress(++m_action, m_totalActions);
    m_progressDlg->addedAction(i18n("index.html created..."), KIPI::SuccessMessage);

    return true;
}

bool SimpleViewerExport::copySimpleViewer()
{
    if(m_canceled)
        return false;

    m_progressDlg->addedAction(i18n("Copying flash files..."), KIPI::StartingMessage);

    QString dataDir;

    // Due to its license, simpleviewer is installed in $KDEHOME
    dataDir = locate("data", "kipiplugin_simpleviewerexport/simpleviewer/");
    if(dataDir.isEmpty())
        installSimpleViewer();
    if(dataDir.isEmpty())
        return false;

    QStringList files;
    QStringList entries;
    QDir dir;

    dir.setPath(dataDir);
    entries = dir.entryList(QDir::Files);
    for(QStringList::Iterator it = entries.begin(); it != entries.end(); ++it) 
    {
        files.append(dir.absPath() + "/" + *it);
    }

    // files distributed with the plugin are installed in $KDEDIRS
    dataDir = locate("data", "kipiplugin_simpleviewerexport/simpleviewer_html/");
    dir.setPath(dataDir);
    entries = dir.entryList(QDir::Files);
    for(QStringList::Iterator it = entries.begin(); it != entries.end(); ++it) 
    {
        files.append(dir.absPath() + "/" + *it);
    }
    // TODO: catch errors
    KIO::CopyJob *copyJob = KIO::copy(files, m_configDlg->exportURL(), true);

    m_progressDlg->addedAction(i18n("flash files copied..."), KIPI::SuccessMessage);

    return true;
}

bool SimpleViewerExport::upload()
{
    if(m_canceled)
        return false;

    m_progressDlg->addedAction(i18n("Uploading gallery..."), KIPI::StartingMessage);

    if(!KIO::NetAccess::dircopy(m_tempDir->name() + "./", m_configDlg->exportURL(), 0))
        return false;

    m_progressDlg->addedAction(i18n("Gallery uploaded..."), KIPI::SuccessMessage);

    return true;
}

bool SimpleViewerExport::checkSimpleViewer() const
{
    return ! locate("data", "kipiplugin_simpleviewerexport/simpleviewer/"+viewer).isEmpty();
}

bool SimpleViewerExport::installSimpleViewer()
{
    FirstRunDlg *firstRunDlg = new FirstRunDlg(kapp->activeWindow());
    if(firstRunDlg->exec() == QDialog::Accepted)
    {
        QString url = firstRunDlg->getURL();
        delete firstRunDlg;

        if(unzip(url))
        {
            return true;
        }
        else
        {
            // ErrorMessage
        }
    }

    return false;
}

bool SimpleViewerExport::unzip(const QString &url)
{
    KZip zip(url);

    if(!openArchive(zip))
    {
        return false;
    }

    return extractArchive(zip);
}

bool SimpleViewerExport::openArchive(KZip &zip)
{
    if(!zip.open(IO_ReadOnly))
    {
        kdDebug() << "open archive failed\n";
        return false;
    }
    return true;
}

bool SimpleViewerExport::extractArchive(KZip &zip)
{
    // read root directory content
    QStringList names = zip.directory()->entries();
    if(names.count() != 1)
    {
        kdDebug() << "Wrong SimpleViewer Version or corrupted archive" << endl;
        kdDebug() << "Content of the archive root folder" << names << endl;
        return false;
    }

    // open root directory
    const KArchiveEntry *root = zip.directory()->entry(names[0]);
    if(!root || !root->isDirectory())
    {
        kdDebug() << "could not open " << names[0] << " of zipname" << endl;
        return false;
    }

    const KArchiveDirectory *dir = dynamic_cast<const KArchiveDirectory*>(root);

    // extract the needed files from SimpleViewer archive
    for(QStringList::Iterator it = m_simpleViewerFiles.begin(); 
        it != m_simpleViewerFiles.end(); ++it ) 
    {
        const KArchiveEntry *entry = dir->entry(*it);
        if(!extractFile(entry))
        {
            //TODO error msg
            kdDebug() << "could not open " << *it << " of zipname" << endl;
            return false;
        }
    }

    return true;
}

bool SimpleViewerExport::extractFile(const KArchiveEntry *entry)
{
    if( !entry || !entry->isFile() ) 
        return false;

    const KArchiveFile *entryFile = dynamic_cast<const KArchiveFile*>(entry);
    QByteArray array = entryFile->data();

    QFile file( m_dataLocal + entry->name() );
    if(file.open( IO_WriteOnly )) 
    {
        int ret = file.writeBlock(array);
        file.close();
        return ret > 0 ? true : false;
    }

    return false;
}

} // namespace KIPISimpleViewerExportPlugin
