/*
	Реализация спецификаций CLDC версии 1.1 (JSR-139), MIDP версии 2.1 (JSR-118)
	и других спецификаций для функционирования компактных приложений на языке
	Java (мидлетов) в среде программного обеспечения Малик Эмулятор.

	Copyright © 2016, 2019 Малик Разработчик

	Это свободная программа: вы можете перераспространять ее и/или изменять
	ее на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
	в каком она была опубликована Фондом свободного программного обеспечения;
	либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.

	Эта программа распространяется в надежде, что она будет полезной,
	но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
	или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
	общественной лицензии GNU.

	Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
	вместе с этой программой. Если это не так, см.
	<https://www.gnu.org/licenses/>.
*/


package malik.emulator.fileformats.graphics.pnglib;

import java.io.*;
import malik.emulator.compression.zlib.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.graphics.*;

public final class PNGEncoder extends Object
		implements OutputAdapter, DataHolder, DataEncoder, ImageEncoder
{
	public static final long PNG_SIGNATURE = 0x89504e470d0a1a0aL;
	private static final int HEADER_SIGNATURE = 0x49484452; /* IHDR */
	private static final int TEXT_SIGNATURE = 0x74455874; /* tEXt */
	private static final int DATA_SIGNATURE = 0x49444154; /* IDAT */
	private static final int END_SIGNATURE = 0x49454e44; /* IEND */


	private boolean alphaSupported;
	private int width;
	private int height;
	private int[] pixels;

	public PNGEncoder()
	{
	}

	public void saveToOutputStream(OutputStream stream)
			throws IOException, EmptyAdapterException
	{
		saveToDataStream(new DataOutputStream(stream));
	}

	public void saveToDataStream(DataOutputStream stream)
			throws IOException, EmptyAdapterException
	{
		boolean alphaSupported;
		int i;
		int j;
		int width;
		int height;
		int offset;
		int length;
		int byteIndex;
		int sbyteIndex;
		int pixelIndex;
		int pixel;
		byte[] chunkBytes;
		int[] pixels;
		Checksum32 checksum;
		ByteArrayOutputStream chunk;
		DataOutputStream chunkOutput;
		if((pixels = this.pixels) == null)
		{
			throw new EmptyAdapterException("Кодирование PNG-файлов: " +
					"адаптер пуст.");
		}
		alphaSupported = this.alphaSupported;
		width = this.width;
		height = this.height;
		checksum = new CRC32();
		chunkOutput = new DataOutputStream(chunk = new ByteArrayOutputStream(0x20));
		stream.writeLong(PNG_SIGNATURE);
		/* Кусок-заголовок */
		chunk.reset();
		chunkOutput.writeInt(HEADER_SIGNATURE);
		chunkOutput.writeInt(width); /* ширина */
		chunkOutput.writeInt(height); /* высота */
		chunkOutput.writeByte(8); /* глубина цвета */
		chunkOutput.writeByte(alphaSupported ? 6 : 2); /* тип пикселов */
		chunkOutput.writeByte(0); /* алгоритм сжатия */
		chunkOutput.writeByte(0); /* алгоритм фильтрации */
		chunkOutput.writeByte(0); /* алгоритм отображения */
		length = (chunkBytes = chunk.toByteArray()).length;
		checksum.reset();
		checksum.update(chunkBytes, 0, length);
		stream.writeInt(length - 4);
		stream.write(chunkBytes);
		stream.writeInt(checksum.getValue());
		/* Кусок-таг */
		chunk.reset();
		chunkOutput.writeInt(TEXT_SIGNATURE);
		chunkOutput.write("Software".getBytes());
		chunkOutput.write(0);
		chunkOutput.write("Malik Emulator https://malik-elaborarer.ru/emulator/".getBytes());
		length = (chunkBytes = chunk.toByteArray()).length;
		checksum.reset();
		checksum.update(chunkBytes, 0, length);
		stream.writeInt(length - 4);
		stream.write(chunkBytes);
		stream.writeInt(checksum.getValue());
		/* Кусок-данные */
		chunkBytes = new byte[height * ((offset = alphaSupported ? 4 : 3) * width + 1)];
		for(byteIndex = 0, pixelIndex = 0, j = 0; j < height; j++)
		{
			for(chunkBytes[byteIndex++] = (byte) 1, i = 0; i < width; i++)
			{
				pixel = pixels[pixelIndex++];
				chunkBytes[byteIndex++] = (byte) (pixel >> 16);
				chunkBytes[byteIndex++] = (byte) (pixel >> 8);
				chunkBytes[byteIndex++] = (byte) pixel;
				if(alphaSupported)
				{
					chunkBytes[byteIndex++] = (byte) (pixel >> 24);
				}
			}
			for(sbyteIndex = byteIndex - 1, i *= offset; i-- > offset; sbyteIndex--)
			{
				chunkBytes[sbyteIndex] -= chunkBytes[sbyteIndex - offset];
			}
		}
		chunk.reset();
		chunkOutput.writeInt(DATA_SIGNATURE);
		chunkOutput.write(Zlib.compress(chunkBytes, 7));
		length = (chunkBytes = chunk.toByteArray()).length;
		checksum.reset();
		checksum.update(chunkBytes, 0, length);
		stream.writeInt(length - 4);
		stream.write(chunkBytes);
		stream.writeInt(checksum.getValue());
		/* Кусок-конец */
		chunk.reset();
		chunkOutput.writeInt(END_SIGNATURE);
		length = (chunkBytes = chunk.toByteArray()).length;
		checksum.reset();
		checksum.update(chunkBytes, 0, length);
		stream.writeInt(length - 4);
		stream.write(chunkBytes);
		stream.writeInt(checksum.getValue());
	}

	public void clear()
	{
		alphaSupported = false;
		width = 0;
		height = 0;
		pixels = null;
	}

	public boolean isEmpty()
	{
		return pixels == null;
	}

	public void setPixels(boolean alphaSupported, int width, int height, int[] pixels)
			throws InvalidDataFormatException
	{
		if(width <= 0 || height <= 0)
		{
			throw new InvalidDataFormatException("Кодирование PNG-файлов: " +
					"размеры могут быть только положительными.");
		}
		if(pixels == null || ((long) width) * ((long) height) > (long) pixels.length)
		{
			throw new InvalidDataFormatException("Кодирование PNG-файлов: " +
					"длина массива пикселов меньше заданных размеров.");
		}
		this.alphaSupported = alphaSupported;
		this.width = width;
		this.height = height;
		this.pixels = pixels;
	}
}
