package org.sunflow.image.readers;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.sunflow.image.Bitmap;
import org.sunflow.image.BitmapReader;
import org.sunflow.image.Color;
import org.sunflow.image.formats.BitmapG8;
import org.sunflow.image.formats.BitmapRGB8;
import org.sunflow.image.formats.BitmapRGBA8;

public class TGABitmapReader implements BitmapReader {
    private static final int[] CHANNEL_INDEX = { 2, 1, 0, 3 };

    public Bitmap load(String filename, boolean isLinear) throws IOException, BitmapFormatException {
        InputStream f = new BufferedInputStream(new FileInputStream(filename));
        byte[] read = new byte[4];

        // read header
        int idsize = f.read();
        int cmaptype = f.read(); // cmap byte (unsupported)
        if (cmaptype != 0)
            throw new BitmapFormatException(String.format("Colormapping (type: %d) is unsupported", cmaptype));
        int datatype = f.read();

        // colormap info (5 bytes ignored)
        f.read();
        f.read();
        f.read();
        f.read();
        f.read();

        f.read(); // xstart, 16 bits (ignored)
        f.read();
        f.read(); // ystart, 16 bits (ignored)
        f.read();

        // read resolution
        int width = f.read();
        width |= f.read() << 8;
        int height = f.read();
        height |= f.read() << 8;

        int bits = f.read();
        int bpp = bits / 8;

        int imgdscr = f.read();

        // skip image ID if present
        if (idsize != 0)
            f.skip(idsize);

        // allocate byte buffer to hold the image
        byte[] pixels = new byte[width * height * bpp];
        if (datatype == 2 || datatype == 3) {
            if (bpp != 1 && bpp != 3 && bpp != 4)
                throw new BitmapFormatException(String.format("Invalid bit depth in uncompressed TGA: %d", bits));
            // uncompressed image
            for (int ptr = 0; ptr < pixels.length; ptr += bpp) {
                // read bytes
                f.read(read, 0, bpp);
                for (int i = 0; i < bpp; i++)
                    pixels[ptr + CHANNEL_INDEX[i]] = read[i];
            }
        } else if (datatype == 10) {
            if (bpp != 3 && bpp != 4)
                throw new BitmapFormatException(String.format("Invalid bit depth in run-length encoded TGA: %d", bits));
            // RLE encoded image
            for (int ptr = 0; ptr < pixels.length;) {
                int rle = f.read();
                int num = 1 + (rle & 0x7F);
                if ((rle & 0x80) != 0) {
                    // rle packet - decode length and copy pixel
                    f.read(read, 0, bpp);
                    for (int j = 0; j < num; j++) {
                        for (int i = 0; i < bpp; i++)
                            pixels[ptr + CHANNEL_INDEX[i]] = read[i];
                        ptr += bpp;
                    }
                } else {
                    // raw packet - decode length and read pixels
                    for (int j = 0; j < num; j++) {
                        f.read(read, 0, bpp);
                        for (int i = 0; i < bpp; i++)
                            pixels[ptr + CHANNEL_INDEX[i]] = read[i];
                        ptr += bpp;
                    }
                }
            }
        } else
            throw new BitmapFormatException(String.format("Unsupported TGA image type: %d", datatype));

        if (!isLinear) {
            // apply reverse correction
            for (int ptr = 0; ptr < pixels.length; ptr += bpp) {
                for (int i = 0; i < 3 && i < bpp; i++)
                    pixels[ptr + i] = Color.NATIVE_SPACE.rgbToLinear(pixels[ptr + i]);
            }
        }

        // should image be flipped in Y?
        if ((imgdscr & 32) == 32) {
            for (int y = 0, pix_ptr = 0; y < (height / 2); y++) {
                int bot_ptr = bpp * (height - y - 1) * width;
                for (int x = 0; x < width; x++) {
                    for (int i = 0; i < bpp; i++) {
                        byte t = pixels[pix_ptr + i];
                        pixels[pix_ptr + i] = pixels[bot_ptr + i];
                        pixels[bot_ptr + i] = t;
                    }
                    pix_ptr += bpp;
                    bot_ptr += bpp;
                }
            }

        }
        f.close();
        switch (bpp) {
            case 1:
                return new BitmapG8(width, height, pixels);
            case 3:
                return new BitmapRGB8(width, height, pixels);
            case 4:
                return new BitmapRGBA8(width, height, pixels);
        }
        throw new BitmapFormatException("Inconsistent code in TGA reader");
    }
}