/*
 ** Copyright 2011, The Android Open Source Project
 **
 ** Licensed under the Apache License, Version 2.0 (the "License");
 ** you may not use this file except in compliance with the License.
 ** You may obtain a copy of the License at
 **
 **     http://www.apache.org/licenses/LICENSE-2.0
 **
 ** Unless required by applicable law or agreed to in writing, software
 ** distributed under the License is distributed on an "AS IS" BASIS,
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */

package com.android.ide.eclipse.gldebugger;

import com.google.protobuf.ByteString;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;

import java.nio.ByteBuffer;
import java.util.Arrays;

public class MessageProcessor {
    static void showError(final String message) {
        // need to call SWT from UI thread
        MessageDialog.openError(null, "MessageProcessor", message);
    }

    /**
     * data layout: uint32 total decompressed length, (chunks: uint32 chunk
     * decompressed size, uint32 chunk compressed size, chunk data)+. 0 chunk
     * compressed size means chunk is not compressed
     */
    public static byte[] lzfDecompressChunks(final ByteString data) {
        ByteBuffer in = data.asReadOnlyByteBuffer();
        in.order(GLFramesView.TARGET_BYTE_ORDER);
        ByteBuffer out = ByteBuffer.allocate(in.getInt());
        byte[] inChunk = new byte[0];
        byte[] outChunk = new byte[0];
        while (in.remaining() > 0) {
            int decompressed = in.getInt();
            int compressed = in.getInt();
            if (decompressed > outChunk.length)
                outChunk = new byte[decompressed];
            if (compressed == 0) {
                in.get(outChunk, 0, decompressed);
                out.put(outChunk, 0, decompressed);
            } else {
                if (compressed > inChunk.length)
                    inChunk = new byte[compressed];
                in.get(inChunk, 0, compressed);
                int size = org.liblzf.CLZF
                        .lzf_decompress(inChunk, compressed, outChunk, outChunk.length);
                assert size == decompressed;
                out.put(outChunk, 0, size);
            }
        }
        assert !out.hasRemaining();
        return out.array();
    }

    /** same data layout as LZFDecompressChunks */
    public static byte[] lzfCompressChunks(final byte[] in, final int inSize) {
        byte[] chunk = new byte[256 * 1024]; // chunk size is arbitrary
        final ByteBuffer out = ByteBuffer.allocate(4 + (inSize + chunk.length - 1)
                / chunk.length * (chunk.length + 4 * 2));
        out.order(GLFramesView.TARGET_BYTE_ORDER);
        out.putInt(inSize);
        for (int i = 0; i < inSize; i += chunk.length) {
            int chunkIn = chunk.length;
            if (i + chunkIn > inSize)
                chunkIn = inSize - i;
            final byte[] inChunk = java.util.Arrays.copyOfRange(in, i, i + chunkIn);
            final int chunkOut = org.liblzf.CLZF
                    .lzf_compress(inChunk, chunkIn, chunk, chunk.length);
            out.putInt(chunkIn);
            out.putInt(chunkOut);
            if (chunkOut == 0) // compressed bigger than chunk (uncompressed)
                out.put(inChunk);
            else
                out.put(chunk, 0, chunkOut);
        }
        return Arrays.copyOf(out.array(), out.position());
    }

    /**
     * returns new ref, which is also the decoded image; ref could be bigger
     * than pixels, in which case the first pixels.length bytes form the image
     */
    public static byte[] decodeReferencedImage(byte[] ref, byte[] pixels) {
        if (ref.length < pixels.length)
            ref = new byte[pixels.length];
        for (int i = 0; i < pixels.length; i++)
            ref[i] ^= pixels[i];
        for (int i = pixels.length; i < ref.length; i++)
            ref[i] = 0; // clear unused ref to maintain consistency
        return ref;
    }

    public static ImageData receiveImage(int width, int height, int format,
            int type, final ByteString data) {
        assert width > 0 && height > 0;
        int bpp = 0;
        int redMask = 0, blueMask = 0, greenMask = 0;
        switch (GLEnum.valueOf(type)) {
            case GL_UNSIGNED_SHORT_5_6_5:
            case GL_UNSIGNED_SHORT_4_4_4_4:
            case GL_UNSIGNED_SHORT_5_5_5_1:
                format = type;
                break;
            case GL_UNSIGNED_BYTE:
                break;
            default:
                showError("unsupported texture type " + type);
                return null;
        }

        switch (GLEnum.valueOf(format)) {
            case GL_ALPHA:
            case GL_LUMINANCE:
                redMask = blueMask = greenMask = 0xff;
                bpp = 8;
                break;
            case GL_LUMINANCE_ALPHA:
                blueMask = 0xff;
                redMask = 0xff00;
                bpp = 16;
                break;
            case GL_RGB:
                blueMask = 0xff;
                greenMask = 0xff00;
                redMask = 0xff0000;
                bpp = 24;
                break;
            case GL_RGBA:
                blueMask = 0xff00;
                greenMask = 0xff0000;
                redMask = 0xff000000;
                bpp = 32;
                break;
            case GL_UNSIGNED_SHORT_5_6_5:
                blueMask = ((1 << 5) - 1) << 0;
                greenMask = ((1 << 6) - 1) << 5;
                redMask = ((1 << 5) - 1) << 11;
                bpp = 16;
                break;
            case GL_UNSIGNED_SHORT_4_4_4_4:
                blueMask = ((1 << 4) - 1) << 4;
                greenMask = ((1 << 4) - 1) << 8;
                redMask = ((1 << 4) - 1) << 12;
                bpp = 16;
                break;
            case GL_UNSIGNED_SHORT_5_5_5_1:
                blueMask = ((1 << 5) - 1) << 1;
                greenMask = ((1 << 5) - 1) << 6;
                redMask = ((1 << 5) - 1) << 11;
                bpp = 16;
                break;
            default:
                showError("unsupported texture format: " + format);
                return null;
        }
        byte[] pixels = lzfDecompressChunks(data);
        assert pixels.length == width * height * (bpp / 8);
        PaletteData palette = new PaletteData(redMask, greenMask, blueMask);
        return new ImageData(width, height, bpp, palette, 1, pixels);
    }
}
