#include "gfxstream/guest/IOStream.h"

#include "GL2Encoder.h"

#include <GLES3/gl31.h>

#include <vector>

#include <assert.h>

namespace gfxstream {
namespace guest {

void IOStream::readbackPixels(void* context, int width, int height, unsigned int format, unsigned int type, void* pixels) {
    GL2Encoder *ctx = (GL2Encoder *)context;
    assert (ctx->state() != NULL);

    int bpp = 0;
    int startOffset = 0;
    int pixelRowSize = 0;
    int totalRowSize = 0;
    int skipRows = 0;

    ctx->state()->getPackingOffsets2D(width, height, format, type,
                                      &bpp,
                                      &startOffset,
                                      &pixelRowSize,
                                      &totalRowSize,
                                      &skipRows);

    size_t pixelDataSize =
        ctx->state()->pixelDataSize(
            width, height, 1, format, type, 1 /* is pack */);

    if (startOffset == 0 &&
        pixelRowSize == totalRowSize) {
        // fast path
        readback(pixels, pixelDataSize);
    } else if (pixelRowSize == totalRowSize && (pixelRowSize == width * bpp)) {
        // fast path but with skip in the beginning
        std::vector<char> paddingToDiscard(startOffset, 0);
        readback(&paddingToDiscard[0], startOffset);
        readback((char*)pixels + startOffset, pixelDataSize - startOffset);
    } else {

        if (startOffset > 0) {
            std::vector<char> paddingToDiscard(startOffset, 0);
            readback(&paddingToDiscard[0], startOffset);
        }
        // need to read back row by row
        size_t paddingSize = totalRowSize - pixelRowSize;
        std::vector<char> paddingToDiscard(paddingSize, 0);

        char* start = (char*)pixels + startOffset;

        for (int i = 0; i < height; i++) {
            if (pixelRowSize > width * bpp) {
                size_t rowSlack = pixelRowSize - width * bpp;
                std::vector<char> rowSlackToDiscard(rowSlack, 0);
                readback(start, width * bpp);
                readback(&rowSlackToDiscard[0], rowSlack);
                readback(&paddingToDiscard[0], paddingSize);
                start += totalRowSize;
            } else {
                readback(start, pixelRowSize);
                readback(&paddingToDiscard[0], paddingSize);
                start += totalRowSize;
            }
        }
    }
}

void IOStream::uploadPixels(void* context, int width, int height, int depth, unsigned int format, unsigned int type, const void* pixels) {
    GL2Encoder *ctx = (GL2Encoder *)context;
    assert (ctx->state() != NULL);

    if (1 == depth) {
        int bpp = 0;
        int startOffset = 0;
        int pixelRowSize = 0;
        int totalRowSize = 0;
        int skipRows = 0;

        ctx->state()->getUnpackingOffsets2D(width, height, format, type,
                &bpp,
                &startOffset,
                &pixelRowSize,
                &totalRowSize,
                &skipRows);

        size_t pixelDataSize =
            ctx->state()->pixelDataSize(
                    width, height, 1, format, type, 0 /* is unpack */);

        if (startOffset == 0 &&
                pixelRowSize == totalRowSize) {
            // fast path
            writeFully(pixels, pixelDataSize);
        } else if (pixelRowSize == totalRowSize && (pixelRowSize == width * bpp)) {
            // fast path but with skip in the beginning
            std::vector<char> paddingToDiscard(startOffset, 0);
            writeFully(&paddingToDiscard[0], startOffset);
            writeFully((char*)pixels + startOffset, pixelDataSize - startOffset);
        } else {

            if (startOffset > 0) {
                std::vector<char> paddingToDiscard(startOffset, 0);
                writeFully(&paddingToDiscard[0], startOffset);
            }
            // need to upload row by row
            size_t paddingSize = totalRowSize - pixelRowSize;
            std::vector<char> paddingToDiscard(paddingSize, 0);

            char* start = (char*)pixels + startOffset;

            for (int i = 0; i < height; i++) {
                if (pixelRowSize > width * bpp) {
                    size_t rowSlack = pixelRowSize - width * bpp;
                    std::vector<char> rowSlackToDiscard(rowSlack, 0);
                    writeFully(start, width * bpp);
                    writeFully(&rowSlackToDiscard[0], rowSlack);
                    writeFully(&paddingToDiscard[0], paddingSize);
                    start += totalRowSize;
                } else {
                    writeFully(start, pixelRowSize);
                    writeFully(&paddingToDiscard[0], paddingSize);
                    start += totalRowSize;
                }
            }
        }
    } else {
        int bpp = 0;
        int startOffset = 0;
        int pixelRowSize = 0;
        int totalRowSize = 0;
        int pixelImageSize = 0;
        int totalImageSize = 0;
        int skipRows = 0;
        int skipImages = 0;

        ctx->state()->getUnpackingOffsets3D(width, height, depth, format, type,
                &bpp,
                &startOffset,
                &pixelRowSize,
                &totalRowSize,
                &pixelImageSize,
                &totalImageSize,
                &skipRows,
                &skipImages);

        size_t pixelDataSize =
            ctx->state()->pixelDataSize(
                    width, height, depth, format, type, 0 /* is unpack */);


        if (startOffset == 0 &&
            pixelRowSize == totalRowSize &&
            pixelImageSize == totalImageSize) {
            // fast path
            writeFully(pixels, pixelDataSize);
        } else if (pixelRowSize == totalRowSize &&
                   pixelImageSize == totalImageSize &&
                   pixelRowSize == (width * bpp)) {
            // fast path but with skip in the beginning
            std::vector<char> paddingToDiscard(startOffset, 0);
            writeFully(&paddingToDiscard[0], startOffset);
            writeFully((char*)pixels + startOffset, pixelDataSize - startOffset);
        } else {

            if (startOffset > 0) {
                std::vector<char> paddingToDiscard(startOffset, 0);
                writeFully(&paddingToDiscard[0], startOffset);
            }
            // need to upload row by row
            size_t paddingSize = totalRowSize - pixelRowSize;
            std::vector<char> paddingToDiscard(paddingSize, 0);

            char* start = (char*)pixels + startOffset;

            size_t imageSlack = totalImageSize - pixelImageSize;
            std::vector<char> imageSlackToDiscard(imageSlack, 0);

            for (int k = 0; k < depth; ++k) {
                for (int i = 0; i < height; i++) {
                    if (pixelRowSize > width * bpp) {
                        size_t rowSlack = pixelRowSize - width * bpp;
                        std::vector<char> rowSlackToDiscard(rowSlack, 0);
                        writeFully(start, width * bpp);
                        writeFully(&rowSlackToDiscard[0], rowSlack);
                        writeFully(&paddingToDiscard[0], paddingSize);
                        start += totalRowSize;
                    } else {
                        writeFully(start, pixelRowSize);
                        writeFully(&paddingToDiscard[0], paddingSize);
                        start += totalRowSize;
                    }
                }
                if (imageSlack > 0) {
                    writeFully(&imageSlackToDiscard[0], imageSlack);
                    start += imageSlack;
                }
            }
        }
    }
}

}  // namespace guest
}  // namespace gfxstream