package com.brackeen.javagamebook.graphics3D.texture;

import java.awt.Graphics2D;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;

/**
   Klasa Texture jest klasą abstrakcyjną, reprezentującą
   teksturę o 16-bitowych kolorach.
*/
public abstract class Texture {

    protected int width;
    protected int height;

    /**
        Tworzy nową teksturę Texture o podanej szerokości i wysokości.
    */
    public Texture(int width, int height) {
        this.width = width;
        this.height = height;
    }


    /**
        Pobiera szerokość tej tekstury Texture.
    */
    public int getWidth() {
        return width;
    }


    /**
        Pobiera wysokość tej tekstury Texture.
    */
    public int getHeight() {
        return height;
    }


    /**
        Pobiera 16-bitowy kolor tej tekstury Texture w podanym
        punkcie (x,y).
    */
    public abstract short getColor(int x, int y);


    /**
        Tworzy niecieniowaną teksturę Texture z podanego pliku obrazu.
    */
    public static Texture createTexture(String filename) {
        return createTexture(filename, false);
    }


    /**
        Tworzy teksturę Texture z podanego pliku obrazu. Jeśli cieniowanie
         jest ustawione jako true, to zwracana jest tekstura ShadedTexture.
    */
    public static Texture createTexture(String filename,
        boolean shaded)
    {
        try {
            return createTexture(ImageIO.read(new File(filename)),
                shaded);
        }
        catch (IOException ex) {
            ex.printStackTrace();
            return null;
        }
    }


    /**
       Tworzy niecieniowaną teksturę Texture z podanego pliku obrazu.
    */
    public static Texture createTexture(
        BufferedImage image)
    {
        return createTexture(image, false);
    }

    /**
        Tworzy teksturę Texture z podanego obrazu. 
    */
    public static Texture createTexture(BufferedImage image, boolean shaded){
        int type = image.getType();
        int width = image.getWidth();
        int height = image.getHeight();

        if (!isPowerOfTwo(width) || !isPowerOfTwo(height)) {
            throw new IllegalArgumentException(
                "Rozmiar tekstury musi być potęgą liczby 2.");
        }

        if (shaded) {
            // konwertuje obraz na obraz indeksowany.
            if (type != BufferedImage.TYPE_BYTE_INDEXED) {
                System.out.println("Warning: image converted to " +
                    "256-color indexed image. Some quality may " +
                    "be lost.");
                BufferedImage newImage = new BufferedImage(
                    image.getWidth(), image.getHeight(),
                    BufferedImage.TYPE_BYTE_INDEXED);
                Graphics2D g = newImage.createGraphics();
                g.drawImage(image, 0, 0, null);
                g.dispose();
                image = newImage;
            }
            DataBuffer dest = image.getRaster().getDataBuffer();
            return new ShadedTexture(
                ((DataBufferByte)dest).getData(),
                countbits(width-1), countbits(height-1),
                (IndexColorModel)image.getColorModel());
        }
        else {

            // konwertuj obraz na obraz o 16-bitowych kolorach
            if (type != BufferedImage.TYPE_USHORT_565_RGB) {
                BufferedImage newImage = new BufferedImage(
                    image.getWidth(), image.getHeight(),
                    BufferedImage.TYPE_USHORT_565_RGB);
                Graphics2D g = newImage.createGraphics();
                g.drawImage(image, 0, 0, null);
                g.dispose();
                image = newImage;
            }

            DataBuffer dest = image.getRaster().getDataBuffer();
            return new PowerOf2Texture(
                ((DataBufferUShort)dest).getData(),
                countbits(width-1), countbits(height-1));
        }
    }


    /**
        Zwraca true jeśli podana liczba jest potęgą liczby 2.
    */
    public static boolean isPowerOfTwo(int n) {
        return ((n & (n-1)) == 0);
    }


    /**
        Zlicza "włączone" bity w liczbie całkowitej.
    */
    public static int countbits(int n) {
        int count = 0;
        while (n > 0) {
            count+=(n & 1);
            n>>=1;
        }
        return count;
    }
}
