/*
	Реализация спецификаций 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.sound.sampled.wavelib;

import java.io.*;
import malik.emulator.fileformats.*;
import malik.emulator.fileformats.sound.*;
import malik.emulator.fileformats.sound.sampled.*;

public final class WaveDecoder extends Object
		implements InputAdapter, SoundDecoder, DataHolder, DataDecoder, SoundDecoderSampled
{
	public static final long RIFF_SIGNATURE = 0x52494646L;
	private static final int WAVE_SIGNATURE = 0x57415645; /* WAVE */
	private static final int FMT_SIGNATURE = 0x666d7420; /* fmt  */
	private static final int DATA_SIGNATURE = 0x64617461; /* data */

	private static int readUnsignedShortLE(InputStream stream)
			throws IOException
	{
		int b1 = stream.read();
		int b2 = stream.read();
		if((b1 | b2) < 0)
		{
			throw new EOFException("DataInputStream: " +
					"достигнут конец данных.");
		}
		return (b2 << 8) | b1;
	}

	private static int readIntLE(InputStream stream)
			throws IOException
	{
		int b1 = stream.read();
		int b2 = stream.read();
		int b3 = stream.read();
		int b4 = stream.read();
		if((b1 | b2 | b3 | b4) < 0)
		{
			throw new EOFException("DataInputStream: " +
					"достигнут конец данных.");
		}
		return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1;
	}


	private boolean stopped;
	private int channels;
	private int samplesPerSecond;
	private int bitsPerSample;
	private byte[] data;
	private short[] samples;

	public WaveDecoder()
	{
	}

	public void loadFromInputStream(InputStream stream)
			throws IOException, InvalidDataFormatException
	{
		loadFromDataStream(new DataInputStream(stream));
	}

	public void loadFromDataStream(DataInputStream stream)
			throws IOException, InvalidDataFormatException
	{
		boolean headerHandled = false;
		boolean dataHandled = false;
		int align = 0;
		int riffSize;
		int chunkStart;
		int chunkLength;
		if((riffSize = readIntLE(stream) - 4) < 0 || stream.readInt() != WAVE_SIGNATURE)
		{
			throw new InvalidDataFormatException("Декодирование Wave-файлов: " +
					"тип файла может быть только WAVE.");
		}
		clear();
		while(riffSize > 0)
		{
			chunkStart = stream.readInt();
			chunkLength = readIntLE(stream);
			if((riffSize -= chunkLength + 8) < 0)
			{
				throw new InvalidDataFormatException("Декодирование Wave-файлов: " +
						"неправильный формат файла.");
			}
			switch(chunkStart)
			{
			default:
				{
					stream.skip(chunkLength);
				}
				break;
			case FMT_SIGNATURE:
				{
					int cm;
					int nc;
					int ss;
					int bs;
					int ba;
					int bt;
					if(chunkLength < 0x10)
					{
						throw new InvalidDataFormatException("Декодирование Wave-файлов: " +
								"маленькая длина важного куска fmt.");
					}
					headerHandled = true;
					cm = readUnsignedShortLE(stream); /* алгоритм сжатия */
					nc = readUnsignedShortLE(stream); /* количество каналов */
					ss = readIntLE(stream); /* частота дискретизации */
					bs = readIntLE(stream); /* количество байт в секунду */
					ba = readUnsignedShortLE(stream); /* выравнивание */
					bt = readUnsignedShortLE(stream); /* разрядность отсчёта */
					if(cm != 1 || (nc != 1 && nc != 2) || (ss <= 0 || ss > 0xffff) ||
							(bt != 8 && bt != 16) || bs != (ss * nc * bt) >> 3 ||
							ba != (nc * bt) >> 3)
					{
						throw new InvalidDataFormatException("Декодирование Wave-файлов: " +
								"система не поддерживает такой формат данных.");
					}
					if((chunkLength -= 0x10) > 0)
					{
						stream.skip((long) chunkLength);
					}
					align = ba;
					this.channels = nc;
					this.samplesPerSecond = ss;
					this.bitsPerSample = bt;
				}
				break;
			case DATA_SIGNATURE:
				{
					byte[] data;
					if(!headerHandled)
					{
						clear();
						throw new InvalidDataFormatException("Декодирование Wave-файлов: " +
								"кусок формата fmt может идти только перед куском данных data.");
					}
					if(chunkLength % align != 0)
					{
						clear();
						throw new InvalidDataFormatException("Декодирование Wave-файлов: " +
								"размер куска данных не кратен выравниванию.");
					}
					dataHandled = true;
					stream.read(data = new byte[chunkLength]);
					this.data = data;
					this.samples = null;
				}
				break;
			}
			if((chunkLength & 0x01) != 0 && riffSize > 0)
			{
				riffSize--;
				stream.skip(1L);
			}
		}
		if(!dataHandled)
		{
			clear();
			throw new InvalidDataFormatException("Декодирование Wave-файлов: " +
					"отсутствует важный кусок данных data.");
		}
	}

	public void stopLoading()
	{
		stopped = true;
	}

	public void clear()
	{
		stopped = false;
		channels = 0;
		samplesPerSecond = 0;
		bitsPerSample = 0;
		data = null;
		samples = null;
	}

	public boolean isEmpty()
	{
		return data == null && samples == null;
	}

	public int getChannels()
	{
		return channels;
	}

	public int getSamplesPerSecond()
	{
		return samplesPerSecond;
	}

	public short[] getSamples()
	{
		boolean stop;
		int i;
		int j;
		int bt;
		int len;
		byte[] data;
		short[] result;
		if((result = this.samples) == null)
		{
			if((data = this.data) == null)
			{
				return null;
			}
			result = new short[len = data.length / (bt = bitsPerSample >> 3)];
			stop = false;
			label0:
			{
				switch(bt)
				{
				case 1:
					for(i = len; i-- > 0; )
					{
						if(stopped)
						{
							stop = true;
							break label0;
						}
						result[i] = (short) ((data[i] ^ 0x80) << 8);
					}
					break;
				case 2:
					for(j = (len - 1) << 1, i = len; i-- > 0; j -= 2)
					{
						if(stopped)
						{
							stop = true;
							break label0;
						}
						result[i] = (short) ((data[j] & 0xff) | (data[j + 1] << 8));
					}
					break;
				}
			}
			if(stop)
			{
				return null;
			}
			this.samples = result;
			this.data = null;
		}
		return result;
	}
}
