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

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

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

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

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

package malik.emulator.fileformats.sound.sampled.wave;

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

public final class WaveDecoder extends ControlList implements SampledSoundDecoder, SampledPlayerDecoder
{
    public static final long SIGNATURE = 0x52494646L; /* RIFF */

    public static final String MIME_TYPE = "audio/x-wav";

    private static final int WAVE = 0x57415645; /* WAVE */
    private static final int FMT  = 0x666d7420; /* fmt  */
    private static final int DATA = 0x64617461; /* data */

    private boolean stopped;
    private int channels;
    private int frequency;
    private short[] samples;

    public WaveDecoder() {
    }

    public void loadFromInputStream(InputStream stream) throws IOException {
        loadFromDataStream(new ExtendedDataInputStream(stream));
    }

    public void loadFromDataStream(ExtendedDataInputStream stream) throws IOException {
        boolean dataHandled = false;
        boolean headerHandled = false;
        int align = 0;
        int bitDepth = 0;
        int soundChannels = 0;
        int soundFrequency = 0;
        long riffSize;
        byte[] soundData = null;
        short[] soundSamples;
        checkStopped();
        if((riffSize = (long) stream.readIntLE() & 0x00000000ffffffffL) < 4L || stream.readInt() != WAVE)
        {
            throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
        }
        for(riffSize -= 4L; riffSize > 0L; )
        {
            int chunkName;
            long chunkSize;
            checkStopped();
            if(riffSize < 8L)
            {
                throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
            }
            chunkName = stream.readInt();
            chunkSize = (long) stream.readIntLE() & 0x00000000ffffffffL;
            if((riffSize -= chunkSize + 8L) < 0L)
            {
                throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
            }
            switch(chunkName)
            {
            default:
                stream.skip(chunkSize);
                break;
            case FMT:
            {
                int cm;
                int nc;
                int ss;
                int bs;
                int ba;
                int bd;
                if(headerHandled || chunkSize < 0x10L)
                {
                    throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
                }
                cm = stream.readUnsignedShortLE(); /* алгоритм сжатия */
                nc = stream.readUnsignedShortLE(); /* количество каналов */
                ss = stream.readIntLE(); /* частота дискретизации */
                bs = stream.readIntLE(); /* байт в секунду */
                ba = stream.readUnsignedShortLE(); /* выравнивание */
                bd = stream.readUnsignedShortLE(); /* глубина отсчёта */
                if(cm != 1 || nc < 1 || nc > 2 || ss <= 0 || ss > 0xffff || bd != 8 && bd != 16)
                {
                    throw new UnsupportedDataException("WaveDecoder.loadFromInputStream: неподдерживаемые данные.");
                }
                if(bs != (ss * nc * bd) >> 3 || ba != (nc * bd) >> 3)
                {
                    throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
                }
                if((chunkSize -= 0x10L) > 0) stream.skip(chunkSize);
                align = ba;
                bitDepth = bd;
                soundChannels = nc;
                soundFrequency = ss;
                headerHandled = true;
                break;
            }
            case DATA:
                if(!headerHandled || dataHandled || chunkSize % (long) align != 0L)
                {
                    throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
                }
                if(chunkSize > (long) Integer.MAX_VALUE)
                {
                    throw new UnsupportedDataException("WaveDecoder.loadFromInputStream: неподдерживаемые данные.");
                }
                stream.read(soundData = new byte[(int) chunkSize]);
                dataHandled = true;
                break;
            }
            if((chunkSize & 1L) != 0L && riffSize > 0L)
            {
                riffSize--;
                stream.skip(1L);
            }
        }
        if(!dataHandled)
        {
            throw new InvalidDataFormatException("WaveDecoder.loadFromInputStream: неправильный формат данных.");
        }
        if(bitDepth == 8)
        {
            int len;
            soundSamples = new short[len = soundData.length];
            for(int i = len; i-- > 0; )
            {
                int byte0 = soundData[i] ^ 0x80;
                if((i & 0x0f) == 0) checkStopped();
                soundSamples[i] = (short) (byte0 << 8);
            }
        } else
        {
            int len;
            soundSamples = new short[len = soundData.length >> 1];
            for(int pos = 0, i = 0; i < len; i++)
            {
                int byte0 = soundData[pos++];
                int byte1 = soundData[pos++];
                if((i & 0x07) == 0) checkStopped();
                soundSamples[i] = (short) (byte1 << 8 | byte0 & 0xff);
            }
        }
        channels = soundChannels;
        frequency = soundFrequency;
        samples = soundSamples;
    }

    public void stopDecoding() {
        stopped = true;
    }

    public void clear() {
        stopped = false;
        channels = 0;
        frequency = 0;
        samples = null;
    }

    public boolean isEmpty() {
        return (channels | frequency) == 0 && samples == null;
    }

    public int getChannels() {
        return channels;
    }

    public int getSamplesPerSecond() {
        return frequency;
    }

    public short[] getSamples() {
        int len;
        short[] result;
        if((result = samples) == null) return null;
        Array.copy(result, 0, result = new short[len = result.length], 0, len);
        return result;
    }

    public String getContentType() {
        return MIME_TYPE;
    }

    private void checkStopped() throws InterruptedIOException {
        if(stopped)
        {
            stopped = false;
            throw new InterruptedIOException("WaveDecoder.loadFromInputStream: процесс декодирования был прерван.");
        }
    }
}
