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

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

public final class SampledPlayer extends CustomPlayer
{
    private static long[] packSamples(short[] samples) {
        int len;
        int pos = 0;
        int sample0;
        int sample1;
        int sample2;
        int sample3;
        int slen = samples == null ? 0 : samples.length;
        long[] result = new long[(len = slen >> 2) + 1];
        for(int i = 0; i < len; i++)
        {
            sample0 = samples[pos++] & 0xffff;
            sample1 = samples[pos++] & 0xffff;
            sample2 = samples[pos++] & 0xffff;
            sample3 = samples[pos++] & 0xffff;
            result[i] = (long) sample0 | (long) sample1 << 0x10 | (long) sample2 << 0x20 | (long) sample3 << 0x30;
        }
        sample0 = (pos < slen ? samples[pos++] : pos > 0 ? samples[pos - 1] : 0) & 0xffff;
        sample1 = (pos < slen ? samples[pos++] : pos > 0 ? samples[pos - 1] : 0) & 0xffff;
        sample2 = (pos < slen ? samples[pos++] : pos > 0 ? samples[pos - 1] : 0) & 0xffff;
        sample3 = (pos < slen ? samples[pos++] : pos > 0 ? samples[pos - 1] : 0) & 0xffff;
        result[len] = (long) sample0 | (long) sample1 << 0x10 | (long) sample2 << 0x20 | (long) sample3 << 0x30;
        return result;
    }

    private final class Playback extends Volume implements SoundPlayerListener
    {
        boolean loaded;
        int position;
        int loopCountSetted;
        int loopCountRemaining;
        long[] packedSamples;
        SampledSoundPlayer systemPlayer;

        public Playback(Object monitor) {
            super(monitor);
            this.loopCountSetted = 1;
            this.loopCountRemaining = 1;
        }

        public void endOfBlock(SoundPlayer player, int blockIndexForLoad) {
            synchronized(monitor)
            {
                label0:
                {
                    int pos;
                    int len;
                    long[] samples;
                    SampledPlayer parent = SampledPlayer.this;
                    len = (samples = packedSamples) == null ? 0 : samples.length;
                    if(blockIndexForLoad < 0)
                    {
                        if(--loopCountRemaining == 0)
                        {
                            loaded = false;
                            parent.playerEndOfMedia();
                            break label0;
                        }
                        parent.playerRestart();
                        try
                        {
                            player.loadBlock(samples, 0, Math.min(len, SampledSoundPlayer.MAX_BLOCK_LENGTH));
                        }
                        catch(SoundPlayerException e)
                        {
                            e.printRealStackTrace();
                        }
                        break label0;
                    }
                    if((pos = position + blockIndexForLoad * SampledSoundPlayer.MAX_BLOCK_LENGTH) >= 0 && pos < len)
                    {
                        try
                        {
                            player.loadBlock(samples, pos, Math.min(len - pos, SampledSoundPlayer.MAX_BLOCK_LENGTH));
                        }
                        catch(SoundPlayerException e)
                        {
                            e.printRealStackTrace();
                        }
                    }
                }
            }
        }

        public void setSystemPlayer(SampledSoundPlayer player) throws SoundPlayerException {
            setupSystemPlayer(player);
            loaded = false;
            systemPlayer = player;
        }

        public void closeSystemPlayer() {
            SoundPlayer player;
            if((player = systemPlayer) != null)
            {
                systemPlayer = null;
                player.close();
            }
        }

        protected void volumeChanged(boolean muted, int level) {
            (SampledPlayer.this).postEvent(PlayerListener.VOLUME_CHANGED, this);
        }

        protected void setupSystemPlayer(SoundPlayer player) throws SoundPlayerException {
            player.setSoundPlayerListener(this);
            super.setupSystemPlayer(player);
        }

        protected SoundPlayer getSystemPlayer() {
            return systemPlayer;
        }
    }

    private final boolean closeNeed;
    private double microsPerPackedSample;
    private int channels;
    private int samplesPerSecond;
    private Playback playback;
    private InputStream stream;
    private final SampledSoundDecoder decoder;

    public SampledPlayer(SampledPlayerDecoder decoder, InputStream stream, boolean closeNeed) {
        int len;
        Control[] controls;
        Playback playback;
        if(decoder == null)
        {
            throw new NullPointerException("SampledPlayer: аргумент decoder равен нулевой ссылке.");
        }
        if(stream == null)
        {
            throw new NullPointerException("SampledPlayer: аргумент stream равен нулевой ссылке.");
        }
        playback = this.new Playback(monitor);
        if((controls = decoder.getControls()) == null || (len = controls.length) <= 0)
        {
            controls = new Control[] { playback };
        } else
        {
            Array.copy(controls, 0, controls = new Control[len + 1], 0, len);
            controls[len] = playback;
        }
        this.controls = controls;
        this.contentType = decoder.getContentType();
        this.closeNeed = closeNeed;
        this.playback = playback;
        this.stream = stream;
        this.decoder = decoder;
    }

    public SampledSoundDecoder getDecoder() {
        return decoder;
    }

    protected void postEvent(String event, Object data) {
        super.postEvent(event, data);
    }

    protected void playerClose() {
        Playback playback;
        if((playback = this.playback) != null)
        {
            playback.closeSystemPlayer();
            playback.packedSamples = null;
        }
        this.playback = null;
        this.stream = null;
        this.decoder.stopDecoding();
    }

    protected void playerRestart() {
        super.playerRestart();
    }

    protected void playerEndOfMedia() {
        super.playerEndOfMedia();
    }

    protected void playerUnrealize() {
        decoder.stopDecoding();
    }

    protected void playerUnprefetch() {
        Playback playback;
        if((playback = this.playback) != null) playback.closeSystemPlayer();
    }

    protected void playerRealize() throws MediaException, InterruptedException {
        Playback playback;
        InputStream stream;
        if((stream = this.stream) != null && (playback = this.playback) != null)
        {
            this.stream = null;
            try
            {
                int c;
                int ss;
                int len;
                long samples;
                SampledSoundDecoder decoder;
                try
                {
                    (decoder = this.decoder).loadFromInputStream(stream);
                }
                finally
                {
                    if(closeNeed) stream.close();
                }
                channels = c = decoder.getChannels();
                samplesPerSecond = ss = decoder.getSamplesPerSecond();
                microsPerPackedSample = 4.e+006d / (double) (samples = (long) c * (long) ss);
                len = (playback.packedSamples = packSamples(decoder.getSamples())).length;
                setDuration(4000000L * (long) len / samples);
            }
            catch(InterruptedIOException e)
            {
                throw new InterruptedException();
            }
            catch(IOException e)
            {
                throw new MediaException(e.getMessage());
            }
        }
    }

    protected void playerPrefetch() throws MediaException {
        Playback playback;
        if((playback = this.playback) != null && playback.systemPlayer == null)
        {
            try
            {
                playback.setSystemPlayer(SampledSoundPlayer.open(channels, samplesPerSecond, SampledSoundPlayer.MAX_BLOCK_LENGTH));
            }
            catch(SoundPlayerException e)
            {
                throw new MediaException(e.getMessage());
            }
        }
    }

    protected void playerStart() throws MediaException {
        long[] samples;
        Playback playback;
        SoundPlayer player;
        if((playback = this.playback) != null && (player = playback.systemPlayer) != null && (samples = playback.packedSamples) != null)
        {
            if(playback.loopCountRemaining == 0) playback.loopCountRemaining = playback.loopCountSetted;
            try
            {
                if(playback.loaded)
                {
                    player.start();
                } else
                {
                    int pos;
                    player.loadBlock(samples, pos = playback.position, Math.min(samples.length - pos, SampledSoundPlayer.MAX_BLOCK_LENGTH));
                    playback.loaded = true;
                }
            }
            catch(SoundPlayerException e)
            {
                throw new MediaException(e.getMessage());
            }
        }
    }

    protected void playerStop() throws MediaException {
        Playback playback;
        SoundPlayer player;
        if((playback = this.playback) != null && (player = playback.systemPlayer) != null)
        {
            try
            {
                player.stop();
            }
            catch(SoundPlayerException e)
            {
                throw new MediaException(e.getMessage());
            }
        }
    }

    protected void playerSetLoopCount(int loopCount) {
        Playback playback;
        if((playback = this.playback) != null)
        {
            playback.loopCountSetted = loopCount;
            playback.loopCountRemaining = loopCount;
        }
    }

    protected long playerSetMediaTime(long mediaTime) throws MediaException {
        long[] samples;
        Playback playback;
        SoundPlayer player;
        if((playback = this.playback) != null && (player = playback.systemPlayer) != null && (samples = playback.packedSamples) != null)
        {
            double microsPS = microsPerPackedSample;
            int pos;
            int lim = samples.length - 1;
            long maxTime = (long) (microsPS * (double) lim);
            if(mediaTime < 0L) mediaTime = 0L;
            if(mediaTime > maxTime) mediaTime = maxTime;
            if((pos = (int) ((double) mediaTime / microsPS)) < 0) pos = 0;
            if(pos > lim) pos = lim;
            playback.position = pos;
            mediaTime = (long) (microsPS * (double) pos);
            try
            {
                switch(getState())
                {
                case PREFETCHED:
                    if(playback.loaded)
                    {
                        player.reset();
                        playback.loaded = false;
                    }
                    break;
                case STARTED:
                    player.reset();
                    player.loadBlock(samples, pos, Math.min(lim - pos + 1, SampledSoundPlayer.MAX_BLOCK_LENGTH));
                    playback.loaded = true;
                    break;
                }
            }
            catch(SoundPlayerException e)
            {
                throw new MediaException(e.getMessage());
            }
        }
        return mediaTime;
    }

    protected long playerGetMediaTime() {
        Playback playback;
        SoundPlayer player;
        if((playback = this.playback) != null && (player = playback.systemPlayer) != null && playback.packedSamples != null)
        {
            int pos;
            if(playback.loaded)
            {
                try
                {
                    long blockpos = player.getCurrentBlockIndexAndPosition();
                    pos = playback.position + (int) (blockpos >> 32) * SampledSoundPlayer.MAX_BLOCK_LENGTH + (int) blockpos;
                }
                catch(SoundPlayerException e)
                {
                    pos = playback.position;
                }
            } else
            {
                pos = playback.position;
            }
            return (long) (microsPerPackedSample * (double) pos);
        }
        return TIME_UNKNOWN;
    }
}
