/**
 * PIC_RW - Qt PIC Support
 * Copyright (C) 2007 Ruben Lopez <r.lopez@bren.es>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * ----------------------------------------------------------------------------
 */

/* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson,
 * and relicensed from GPL to LGPL to accommodate the KDE licensing policy
 * with his permission.
 * These is the original copyright:
 * Copyright (C) 1998 Halfdan Ingvarsson
 */

#include "pic_rw.h"
#include <iostream>
#include <qimage.h>
#include <qendian.h>

/**
 * Writes the PIC header info.
 * @param dev IO Device
 * @param msg Header message
 * @param width Image width
 * @param height Image height
 * @param alpha Image has alpha?
 * @return True on success
 */
static bool writeHeader(QIODevice *dev, std::string msg, unsigned width, unsigned height, bool alpha)
{
    PICHeader h;
    PICChannel c;
    unsigned count = 0;

    memset(&h, 0, sizeof(PICHeader));
    h.magic = qToBigEndian<qint32>(PIC_MAGIC_NUMBER);
    h.version = 3.71f;
    strcpy(h.comment, msg.c_str());
    strncpy(h.id, "PICT", 4);
    h.width = qToBigEndian<qint16>(width);
    h.height = qToBigEndian<qint16>(height);
    h.ratio = 1.0f;
    h.fields = qToBigEndian<qint16>(BOTH);
    count = dev->write((const char *) & h, sizeof(PICHeader));
    if (count != sizeof(PICHeader)) {
        return false;
    }

    memset(&c, 0, sizeof(PICChannel));
    c.size = 8;
    c.type = RLE;
    c.channel = RED | GREEN | BLUE;
    if (alpha) {
        c.chained = 1;
    }
    count = dev->write((const char *) & c, sizeof(PICChannel));
    if (count != sizeof(PICChannel)) {
        return false;
    }

    if (alpha) {
        c.channel = ALPHA;
        c.chained = 0;
        count = dev->write((const char *) & c, sizeof(PICChannel));
        if (count != sizeof(PICChannel)) {
            return false;
        }
    }
    return true;
}

inline unsigned convertABGRtoRGBA(unsigned pixel)
{
    unsigned r = pixel & 0xFF;
    unsigned g = (pixel >> 8) & 0xFF;
    unsigned b = (pixel >> 16) & 0xFF;
    unsigned a = (pixel >> 24) & 0xFF;
    return a | (b << 8) | (g << 16) | (r << 24);
}

/**
 * Encodes a portion of the image in RLE coding
 * @param image The image that we want to encode
 * @param output The output buffer
 * @param channels The number of channels to write
 * @param offset Offset in bytes to copy
 * @param max The maximum number of pixels to write
 * @param oConsumed The number of pixels consumed from image
 * @param oProduced The number of bytes produced in out
 * @return True on success
 */
static bool encodeRLE(const unsigned *image, unsigned char *output, bool rgb, unsigned max, unsigned &oConsumed, unsigned &oProduced)
{
    const unsigned *in = image;
    unsigned char *out = output;
    unsigned count = 0;
    unsigned channels = 3;
    unsigned offset = 1;
    unsigned mask = 0x00FFFFFF;
    if (!rgb) {
        channels = 1;
        offset = 0;
        mask = 0xFF000000;
    }
    for (; (*in & mask) == (*image & mask) && count < 65536 && count < max; in++, count++) {
    }
    if (count > 127) {
        /* Sequence of > 127 identical pixels */
        *out++ = 128;
        *out++ = count >> 8;
        *out++ = count & 0xFF;
        unsigned pixel = convertABGRtoRGBA(*image);
        memcpy(out, ((char *) & pixel) + offset, channels);
        out += channels;
        oConsumed = count;
        oProduced = out - output;
    } else if (count > 1) {
        /* Sequece of < 128 identical pixels */
        *out++ = (count + 127);
        unsigned pixel = convertABGRtoRGBA(*image);
        memcpy(out, ((char *) & pixel) + offset, channels);
        out += channels;
        oConsumed = count;
        oProduced = out - output;
    } else {
        in = image + 1;
        unsigned previous = *image;
        count = 0;
        while ((*in & mask) != (previous & mask) && count < 128 && count < max) {
            previous = *in;
            in++;
            count++;
        }
        // This only happens when it is the end of the row, and it is ok
        if (count == 0) {
            count = 1;
        }
        *out++ = (count - 1);
        in = image;
        for (unsigned c = 0; c < count; ++c) {
            unsigned pixel = convertABGRtoRGBA(*in);
            memcpy(out, ((char *) & pixel) + offset, channels);
            out += channels;
            in++;
        }
        oConsumed = count;
        oProduced = out - output;
    }
    return true;
}

/**
 * Writes a row to the file
 * @return True on success
 */
static bool writeRow(QIODevice *dev, unsigned *row, unsigned width, bool alpha)
{
    unsigned char *buf = new unsigned char[width * 4];
    unsigned posIn = 0;
    unsigned posOut = 0;

    memset(buf, 0, width * 4);

    unsigned consumed = 0;
    unsigned produced = 0;

    /* Write the RGB part of the scanline */
    while (posIn < width) {
        if (!encodeRLE(row + posIn, buf + posOut, true, width - posIn, consumed, produced)) {
            delete[] buf;
            return false;
        }
        posIn += consumed;
        posOut += produced;
    }

    /* Write the alpha channel */
    if (alpha) {
        posIn = 0;
        while (posIn < width) {
            if (!encodeRLE(row + posIn, buf + posOut, false, width - posIn, consumed, produced)) {
                delete[] buf;
                return false;
            }
            posIn += consumed;
            posOut += produced;
        }
    }

    dev->write((const char *) buf, posOut);
    delete[] buf;
    return true;
}

#define FAIL() { \
        std::cout << "ERROR Writing PIC!" << std::endl; \
        return; \
    }

/// Pic write handler for Qt / KDE

void pic_write(QIODevice *dev, const QImage *img)
{
    bool alpha = img->hasAlphaChannel();
    if (!writeHeader(dev, "Created with KDE", img->width(), img->height(), alpha)) {
        FAIL();
    }

    for (int r = 0; r < img->height(); r++) {
        unsigned *row = (unsigned *) img->scanLine(r);
        if (!writeRow(dev, row, img->width(), alpha)) {
            FAIL();
        }
    }
}
