package com.jme3.scene.plugins.blender.textures.blending;

import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.textures.TexturePixel;
import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory;
import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput;
import com.jme3.texture.Image;
import com.jme3.texture.Image.Format;
import com.jme3.texture.image.ColorSpace;
import com.jme3.util.BufferUtils;

import java.nio.ByteBuffer;
import java.util.ArrayList;

import jme3tools.converters.RGB565;

/**
 * <p>
 * The class that is responsible for blending the following texture types:
 * </p>
 * <ul>
 * <li>DXT1</li>
 * <li>DXT1A</li>
 * <li>DXT3</li>
 * <li>DXT5</li>
 * </ul>
 * @author Marcin Roguski (Kaelthas)
 */
public class TextureBlenderDDS extends TextureBlenderAWT {
    public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) {
        super(flag, negateTexture, blendType, materialColor, color, blendFactor);
    }

    @Override
    public Image blend(Image image, Image baseImage, BlenderContext blenderContext) {
        this.prepareImagesForBlending(image, baseImage);

        Format format = image.getFormat();
        int width = image.getWidth();
        int height = image.getHeight();
        int depth = image.getDepth();
        if (depth == 0) {
            depth = 1;
        }
        ArrayList<ByteBuffer> dataArray = new ArrayList<ByteBuffer>(depth);

        PixelInputOutput basePixelIO = null;
        float[][] compressedMaterialColor = null;
        TexturePixel[] baseTextureColors = null;
        if (baseImage != null) {
            basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat());
            compressedMaterialColor = new float[2][4];
            baseTextureColors = new TexturePixel[] { new TexturePixel(), new TexturePixel() };
        }

        float[] resultPixel = new float[4];
        float[] pixelColor = new float[4];
        TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel() };
        int baseXTexelIndex = 0, baseYTexelIndex = 0;
        float[] alphas = new float[] { 1, 1 };
        for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) {
            ByteBuffer data = image.getData(dataLayerIndex);
            data.rewind();
            ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining());
            while (data.hasRemaining()) {
                if (format == Format.DXT3) {
                    long alpha = data.getLong();
                    // get alpha for first and last pixel that is compressed in the texel
                    byte alpha0 = (byte) (alpha << 4 & 0xFF);
                    byte alpha1 = (byte) (alpha >> 60 & 0xFF);
                    alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
                    alphas[1] = alpha1 >= 0 ? alpha1 / 255.0f : 1.0f - ~alpha1 / 255.0f;
                    newData.putLong(alpha);
                } else if (format == Format.DXT5) {
                    byte alpha0 = data.get();
                    byte alpha1 = data.get();
                    alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
                    alphas[1] = alpha1 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f;
                    newData.put(alpha0);
                    newData.put(alpha1);
                    // only read the next 6 bytes (these are alpha indexes)
                    newData.putInt(data.getInt());
                    newData.putShort(data.getShort());
                }
                int col0 = RGB565.RGB565_to_ARGB8(data.getShort());
                int col1 = RGB565.RGB565_to_ARGB8(data.getShort());
                colors[0].fromARGB8(col0);
                colors[1].fromARGB8(col1);

                // compressing 16 pixels from the base texture as if they belonged to a texel
                if (baseImage != null) {
                    // reading pixels (first and last of the 16 colors array)
                    basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);// first pixel
                    basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);// last pixel
                    baseTextureColors[0].toRGBA(compressedMaterialColor[0]);
                    baseTextureColors[1].toRGBA(compressedMaterialColor[1]);
                }

                // blending colors
                for (int i = 0; i < colors.length; ++i) {
                    if (negateTexture) {
                        colors[i].negate();
                    }
                    colors[i].toRGBA(pixelColor);
                    pixelColor[3] = alphas[i];
                    this.blendPixel(resultPixel, compressedMaterialColor != null ? compressedMaterialColor[i] : materialColor, pixelColor, blenderContext);
                    colors[i].fromARGB(1, resultPixel[0], resultPixel[1], resultPixel[2]);
                    int argb8 = colors[i].toARGB8();
                    short rgb565 = RGB565.ARGB8_to_RGB565(argb8);
                    newData.putShort(rgb565);
                }

                // just copy the remaining 4 bytes of the current texel
                newData.putInt(data.getInt());

                ++baseXTexelIndex;
                if (baseXTexelIndex > image.getWidth() >> 2) {
                    baseXTexelIndex = 0;
                    ++baseYTexelIndex;
                }
            }
            dataArray.add(newData);
        }

        Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray, ColorSpace.Linear) : new Image(format, width, height, dataArray.get(0), ColorSpace.Linear);
        if (image.getMipMapSizes() != null) {
            result.setMipMapSizes(image.getMipMapSizes().clone());
        }
        return result;
    }
}
