
#include "Imaging.h"

#include <assert.h>
#include <string.h>

static int
comparePixels(const UINT8 *buf, int x, int bytesPerPixel) {
    buf += x * bytesPerPixel;
    return memcmp(buf, buf + bytesPerPixel, bytesPerPixel) == 0;
}

int
ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
    UINT8 *dst;
    int bytesPerPixel;

    if (state->state == 0) {
        if (state->ystep < 0) {
            state->ystep = -1;
            state->y = state->ysize - 1;
        } else {
            state->ystep = 1;
        }

        state->state = 1;
    }

    dst = buf;
    bytesPerPixel = (state->bits + 7) / 8;

    while (1) {
        int flushCount;

        /*
         * state->count is the numbers of bytes in the packet,
         * excluding the 1-byte descriptor.
         */
        if (state->count == 0) {
            UINT8 *row;
            UINT8 descriptor;
            int startX;

            assert(state->x <= state->xsize);

            /* Make sure we have space for the descriptor. */
            if (bytes < 1) {
                break;
            }

            if (state->x == state->xsize) {
                state->x = 0;

                state->y += state->ystep;
                if (state->y < 0 || state->y >= state->ysize) {
                    state->errcode = IMAGING_CODEC_END;
                    break;
                }
            }

            if (state->x == 0) {
                state->shuffle(
                    state->buffer,
                    (UINT8 *)im->image[state->y + state->yoff] +
                        state->xoff * im->pixelsize,
                    state->xsize
                );
            }

            row = state->buffer;

            /* Start with a raw packet for 1 px. */
            descriptor = 0;
            startX = state->x;
            state->count = bytesPerPixel;

            if (state->x + 1 < state->xsize) {
                int maxLookup;
                int isRaw;

                isRaw = !comparePixels(row, state->x, bytesPerPixel);
                ++state->x;

                /*
                 * A packet can contain up to 128 pixels;
                 * 2 are already behind (state->x points to
                 * the second one).
                 */
                maxLookup = state->x + 126;
                /* A packet must not span multiple rows. */
                if (maxLookup > state->xsize - 1) {
                    maxLookup = state->xsize - 1;
                }

                if (isRaw) {
                    while (state->x < maxLookup) {
                        if (!comparePixels(row, state->x, bytesPerPixel)) {
                            ++state->x;
                        } else {
                            /* Two identical pixels will go to RLE packet. */
                            --state->x;
                            break;
                        }
                    }

                    state->count += (state->x - startX) * bytesPerPixel;
                } else {
                    descriptor |= 0x80;

                    while (state->x < maxLookup) {
                        if (comparePixels(row, state->x, bytesPerPixel)) {
                            ++state->x;
                        } else {
                            break;
                        }
                    }
                }
            }

            /*
             * state->x currently points to the last pixel to be
             * included in the packet. The pixel count in the
             * descriptor is 1 less than actual number of pixels in
             * the packet, that is, state->x == startX if we encode
             * only 1 pixel.
             */
            descriptor += state->x - startX;
            *dst++ = descriptor;
            --bytes;

            /* Advance to past-the-last encoded pixel. */
            ++state->x;
        }

        assert(bytes >= 0);
        assert(state->count > 0);
        assert(state->x > 0);
        assert(state->count <= state->x * bytesPerPixel);

        if (bytes == 0) {
            break;
        }

        flushCount = state->count;
        if (flushCount > bytes) {
            flushCount = bytes;
        }

        memcpy(
            dst, state->buffer + (state->x * bytesPerPixel - state->count), flushCount
        );
        dst += flushCount;
        bytes -= flushCount;

        state->count -= flushCount;
    }

    return dst - buf;
}
