package com.brackeen.javagamebook.graphics3D.texture;

import java.awt.Color;
import java.awt.image.IndexColorModel;

/**
    Klasa ShadedTexture jest podklasą klasy Texture z cieniowanymi
    wariantami tekstury. Źródłowa tekstura przechowywana jest jako 8-bitowy
    obraz, który dla każdego odcienia posiada osobną paletę.
*/
public final class ShadedTexture extends Texture {

    public static final int NUM_SHADE_LEVELS = 64;
    public static final int MAX_LEVEL = NUM_SHADE_LEVELS-1;

    private static final int PALETTE_SIZE_BITS = 8;
    private static final int PALETTE_SIZE = 1 << PALETTE_SIZE_BITS;

    private byte[] buffer;
    private IndexColorModel palette;
    private short[] shadeTable;
    private int defaultShadeLevel;
    private int widthBits;
    private int widthMask;
    private int heightBits;
    private int heightMask;

    // wiersz jest zapisywany za pomocą setCurrRow i pobierany za pomocą getColorCurrRow
    private int currRow;

    /**
        Tworzy nową teksturę ShadedTexture z podanej palety i 
        8-bitowego bufora obrazu. Szerokość mapy bitowej to 2 
        do potęgi widthBits, czyli (1 << widthBits). Podobnie, 
        wysokość mapy bitowej to 2 do potęgi heightBits, czyli
        (1 << heightBits). Tekstura cieniowana jest od jej 
        oryginalnego koloru do czerni. 
    */
    public ShadedTexture(byte[] buffer,
        int widthBits, int heightBits,
        IndexColorModel palette)
    {
        this(buffer, widthBits, heightBits, palette, Color.BLACK);
    }


    /**
        Tworzy nową teksturę ShadedTexture z podanej palety i 
        8-bitowego bufora obrazu i docelowego odcienia. Szerokość 
        mapy bitowej to 2 do potęgi widthBits, czyli (1 << widthBits). 
        Podobnie, wysokość mapy bitowej to 2 do potęgi heightBits, 
        czyli (1 << heightBits). Tekstura cieniowana jest od jej 
        oryginalnego koloru do docelowego odcienia. 
    */
    public ShadedTexture(byte[] buffer,
        int widthBits, int heightBits,
        IndexColorModel palette, Color targetShade)
    {
        super(1 << widthBits, 1 << heightBits);
        this.buffer = buffer;
        this.widthBits = widthBits;
        this.heightBits = heightBits;
        this.widthMask = getWidth() - 1;
        this.heightMask = getHeight() - 1;
        this.buffer = buffer;
        this.palette = palette;
        defaultShadeLevel = MAX_LEVEL;

        makeShadeTable(targetShade);
    }


    /**
        Tworzy tablicę odcieni dla tej tekstury ShadedTexture. Każda
        pozycja w palecie będzie stopniowo cieniowana od oryginalnego
        koloru do podanego odcienia.
    */
    public void makeShadeTable(Color targetShade) {

        shadeTable = new short[NUM_SHADE_LEVELS*PALETTE_SIZE];

        for (int level=0; level<NUM_SHADE_LEVELS; level++) {
            for (int i=0; i<palette.getMapSize(); i++) {
                int red = calcColor(palette.getRed(i),
                    targetShade.getRed(), level);
                int green = calcColor(palette.getGreen(i),
                    targetShade.getGreen(), level);
                int blue = calcColor(palette.getBlue(i),
                    targetShade.getBlue(), level);

                int index = level * PALETTE_SIZE + i;
                // RGB 5:6:5
                shadeTable[index] = (short)(
                            ((red >> 3) << 11) |
                            ((green >> 2) << 5) |
                            (blue >> 3));
            }
        }
    }

    private int calcColor(int palColor, int target, int level) {
        return (palColor - target) * (level+1) /
            NUM_SHADE_LEVELS + target;
    }


    /**
        Ustawia domyślny poziom cieniowania wykorzystywany, gdy 
        przywoływana jest metoda getColor().
    */
    public void setDefaultShadeLevel(int level) {
        defaultShadeLevel = level;
    }


    /**
        Pobiera domyślny poziom cieniowania wykorzystywany, gdy 
        przywoływana jest metoda getColor().
    */
    public int getDefaultShadeLevel() {
        return defaultShadeLevel;
    }


    /**
        Pobiera 16-bitowy kolor tej tekstury Texture w podanej
        lokacji (x,y), wykorzystując domyślny poziom cieniowania.
    */
    public short getColor(int x, int y) {
        return getColor(x, y, defaultShadeLevel);
    }


    /**
        Pobiera 16-bitowy kolor tej tekstury Texture w podanej
        lokacji (x,y), wykorzystując podany poziom cieniowania.
    */
    public short getColor(int x, int y, int shadeLevel) {
        return shadeTable[(shadeLevel << PALETTE_SIZE_BITS) |
            (0xff & buffer[
            (x & widthMask) |
            ((y & heightMask) << widthBits)])];
    }

    /**
        Ustawia bieżący wiersz dla getColorCurrRow(). Wstępnie wylicza
        przesunięcie dla tego wiersza.
    */
    public void setCurrRow(int y) {
        currRow = (y & heightMask) << widthBits;
    }


    /**
        Pobiera kolor w podanej lokacji x i o określonym poziomie
        cieniowania. Wykorzystywany jest wiersz definiowany 
        w metodzie setCurrRow.
    */
    public short getColorCurrRow(int x, int shadeLevel) {
        return shadeTable[(shadeLevel << PALETTE_SIZE_BITS) |
            (0xff & buffer[(x & widthMask) | currRow])];
    }

}
