/* ============================================================
 *
 * This file is a part of digiKam project
 * http://www.digikam.org
 *
 * Date        : 2003-10-14
 * Description : digiKam TDEIO thumbnails generator interface
 *
 * Copyright (C) 2003-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
 * Copyright (C) 2006-2007 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/ipc.h>
#include <sys/shm.h>
#include <fcntl.h>
#include <unistd.h>
}

// TQt includes.

#include <tqstring.h>
#include <tqdir.h>
#include <tqfileinfo.h>
#include <tqimage.h>
#include <tqpixmap.h>
#include <tqpainter.h>
#include <tqcolor.h>
#include <tqdatastream.h>

// KDE includes.

#include <tdeglobal.h>

// Local includes.

#include "ddebug.h"
#include "thumbnailjob.h"
#include "thumbnailjob.moc"

namespace Digikam
{

class ThumbnailJobPriv
{
public:

    bool        highlight;
    bool        exifRotate;
    bool        running;

    int         size;

    // Shared memory segment Id. The segment is allocated to a size
    // of extent x extent x 4 (32 bit image) on first need.
    int         shmid;

    // And the data area
    uchar      *shmaddr;

    KURL        curr_url;
    KURL        next_url;
    KURL::List  urlList;
};

ThumbnailJob::ThumbnailJob(const KURL& url, int size,
                           bool highlight, bool exifRotate)
            : TDEIO::Job(false)
{
    d = new ThumbnailJobPriv;

    d->urlList.append(url);

    d->size       = size;
    d->highlight  = highlight;
    d->exifRotate = exifRotate;
    d->curr_url   = d->urlList.first();
    d->next_url   = d->curr_url;
    d->running    = false;
    d->shmid      = -1;
    d->shmaddr    = 0;

    processNext();
}

ThumbnailJob::ThumbnailJob(const KURL::List& urlList, int size,
                           bool highlight, bool exifRotate)
            : TDEIO::Job(false)
{
    d = new ThumbnailJobPriv;

    d->urlList    = urlList;
    d->size       = size;
    d->highlight  = highlight;
    d->running    = false;
    d->exifRotate = exifRotate;
    d->curr_url   = d->urlList.first();
    d->next_url   = d->curr_url;
    d->shmid      = -1;
    d->shmaddr    = 0;

    processNext();
}

ThumbnailJob::~ThumbnailJob()
{
    if (d->shmaddr)
    {
        shmdt((char*)d->shmaddr);
        shmctl(d->shmid, IPC_RMID, 0);
    }

    delete d;
}

void ThumbnailJob::addItem(const KURL& url)
{
    d->urlList.append(url);

    if (!d->running && subjobs.isEmpty())
        processNext();
}

void ThumbnailJob::addItems(const KURL::List& urlList)
{
    for (KURL::List::const_iterator it = urlList.begin();
         it != urlList.end(); ++it)
    {
        d->urlList.append(*it);
    }

    if (!d->running && subjobs.isEmpty())
        processNext();
}

bool ThumbnailJob::setNextItemToLoad(const KURL& url)
{
    KURL::List::const_iterator it = d->urlList.find(url);
    if (it != d->urlList.end())
    {
        d->next_url = *it;
        return true;
    }

    return false;
}

void ThumbnailJob::removeItem(const KURL& url)
{
    d->urlList.remove(url);
}

void ThumbnailJob::processNext()
{
    if (d->urlList.isEmpty())
    {
        d->running = false;
        emit signalCompleted();
        return;
    }

    KURL::List::iterator it = d->urlList.find(d->next_url);
    if (it == d->urlList.end())
    {
        it = d->urlList.begin();
    }

    d->curr_url = *it;
    it = d->urlList.remove(it);
    if (it != d->urlList.end())
    {
        d->next_url = *it;
    }
    else
    {
        d->next_url = KURL();
    }

    KURL url(d->curr_url);
    url.setProtocol("digikamthumbnail");

    TDEIO::TransferJob *job = TDEIO::get(url, false, false);
    job->addMetaData("size", TQString::number(d->size));
    createShmSeg();

    if (d->shmid != -1)
        job->addMetaData("shmid", TQString::number(d->shmid));

    // Rotate thumbnail accordindly with Exif rotation tag if necessary.
    if (d->exifRotate)
        job->addMetaData("exif", "yes");

    connect(job, TQ_SIGNAL(data(TDEIO::Job *, const TQByteArray &)),
            this, TQ_SLOT(slotThumbData(TDEIO::Job *, const TQByteArray &)));

    addSubjob(job);
    d->running = true;
}

void ThumbnailJob::slotResult(TDEIO::Job *job)
{
    subjobs.remove(job);
    Q_ASSERT( subjobs.isEmpty() );

    if (job->error())
    {
        emit signalFailed(d->curr_url);
    }

    d->running  = false;
    processNext();
}

void ThumbnailJob::createShmSeg()
{
    if (d->shmid == -1)
    {
        if (d->shmaddr) 
        {
            shmdt((char*)d->shmaddr);
            shmctl(d->shmid, IPC_RMID, 0);
        }

        d->shmid = shmget(IPC_PRIVATE, 256 * 256 * 4, IPC_CREAT|0600);
        if (d->shmid != -1)
        {
            d->shmaddr = static_cast<uchar *>(shmat(d->shmid, 0, SHM_RDONLY));
            if (d->shmaddr == (uchar *)-1)
            {
                shmctl(d->shmid, IPC_RMID, 0);
                d->shmaddr = 0;
                d->shmid = -1;
            }
        }
        else
            d->shmaddr = 0;
    }
}

void ThumbnailJob::slotThumbData(TDEIO::Job*, const TQByteArray &data)
{
    if (data.isEmpty())
        return;

    TQImage thumb;
    TQDataStream stream(data, IO_ReadOnly);
    if (d->shmaddr)
    {
        int width, height, depth;
        stream >> width >> height >> depth;
        thumb = TQImage(d->shmaddr, width, height, depth,
                       0, 0, TQImage::IgnoreEndian);

        // The buffer supplied to the TQImage constructor above must remain valid
        // throughout the lifetime of the object.
        // This is not true, the shared memory will be freed or reused.
        // If we pass the object around, we must do a deep copy.
        thumb = thumb.copy();
    }
    else
    {
        stream >> thumb;
    }

    if (thumb.isNull()) 
    {
        DWarning() << k_funcinfo << "thumbnail is null" << endl;
        emit signalFailed(d->curr_url);
        return;
    }

    emitThumbnail(thumb);
}

void ThumbnailJob::emitThumbnail(TQImage& thumb)
{
    if (thumb.isNull())
    {
        return;
    }

    TQPixmap pix(thumb);

    int w = pix.width();
    int h = pix.height();

    // highlight only when requested and when thumbnail
    // width and height are greater than 10
    if (d->highlight && (w >= 10 && h >= 10))
    {
        TQPainter p(&pix);
        p.setPen(TQPen(TQColor(0,0,0),1));
        p.drawRect(0,0,w,h);
        p.end();
    }

    emit signalThumbnail(d->curr_url, pix);
}

}  // namespace Digikam


