/*
    Реализация спецификаций 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 javax.microedition.media.control.*;
import malik.emulator.fileformats.sound.synthetic.*;
import malik.emulator.media.sound.*;
import malik.emulator.microedition.media.*;

public final class SyntheticPlayer extends CustomPlayer
{
    private final class Playback extends Volume implements SoundPlayerListener, MIDIControl
    {
        boolean loaded;
        int position;
        int loopCountSetted;
        int loopCountRemaining;
        long[] synthesizerMessages;
        SyntheticSoundPlayer systemPlayer;

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

        public void endOfBlock(SoundPlayer player, int blockIndexForLoad) {
            if(blockIndexForLoad >= 0) return;
            synchronized(monitor)
            {
                label0:
                {
                    SyntheticPlayer parent = SyntheticPlayer.this;
                    if(--loopCountRemaining == 0)
                    {
                        parent.playerEndOfMedia();
                        break label0;
                    }
                    parent.playerRestart();
                    try
                    {
                        player.start();
                    }
                    catch(SoundPlayerException e)
                    {
                        e.printRealStackTrace();
                    }
                }
            }
        }

        public void shortMidiEvent(int statusByte, int dataByte1, int dataByte2) {
            SyntheticSoundPlayer player;
            if(statusByte < 0x80 || statusByte >= 0xf0 && statusByte <= 0xf7 || statusByte > 0xff)
            {
                throw new IllegalArgumentException("MIDIControl.shortMidiEvent: аргумент statusByte имеет недопустимое значение.");
            }
            if(dataByte1 < 0x00 || dataByte1 > 0x7f)
            {
                throw new IllegalArgumentException("MIDIControl.shortMidiEvent: аргумент dataByte1 имеет недопустимое значение.");
            }
            if(dataByte2 < 0x00 || dataByte2 > 0x7f)
            {
                throw new IllegalArgumentException("MIDIControl.shortMidiEvent: аргумент dataByte2 имеет недопустимое значение.");
            }
            if((player = systemPlayer) == null)
            {
                throw new IllegalStateException("MIDIControl.shortMidiEvent: проигрыватель не получил доступ к ресурсам.");
            }
            try
            {
                player.sendMessage(statusByte << 16 | dataByte1 << 8 | dataByte2);
            }
            catch(SoundPlayerException e)
            {
                throw new IllegalStateException("MIDIControl.shortMidiEvent: проигрыватель закрыт.");
            }
        }

        public void setChannelVolume(int channel, int volume) {
            SyntheticSoundPlayer player;
            if(channel < 0x00 || channel > 0x0f)
            {
                throw new IllegalArgumentException("MIDIControl.setChannelVolume: аргумент channel имеет недопустимое значение.");
            }
            if(volume < 0x00 || volume > 0x7f)
            {
                throw new IllegalArgumentException("MIDIControl.setChannelVolume: аргумент volume имеет недопустимое значение.");
            }
            if((player = systemPlayer) == null)
            {
                throw new IllegalStateException("MIDIControl.setChannelVolume: проигрыватель не получил доступ к ресурсам.");
            }
            try
            {
                player.sendMessage((CONTROL_CHANGE | channel) << 16 | CONTROL_MAIN_VOLUME << 8 | volume);
            }
            catch(SoundPlayerException e)
            {
                throw new IllegalStateException("MIDIControl.setChannelVolume: проигрыватель закрыт.");
            }
        }

        public void setProgram(int channel, int bank, int programme) {
            SyntheticSoundPlayer player;
            if(channel < 0x00 || channel > 0x0f)
            {
                throw new IllegalArgumentException("MIDIControl.setProgram: аргумент channel имеет недопустимое значение.");
            }
            if(bank < -1 || bank > 0x3fff)
            {
                throw new IllegalArgumentException("MIDIControl.setProgram: аргумент bank имеет недопустимое значение.");
            }
            if(programme < 0x00 || programme > 0x7f)
            {
                throw new IllegalArgumentException("MIDIControl.setProgram: аргумент programme имеет недопустимое значение.");
            }
            if((player = systemPlayer) == null)
            {
                throw new IllegalStateException("MIDIControl.setProgram: проигрыватель не получил доступ к ресурсам.");
            }
            try
            {
                if(bank >= 0)
                {
                    player.sendMessage((CONTROL_CHANGE | channel) << 16 | CONTROL_BANK_CHANGE_MSB << 8 | bank >> 7);
                    player.sendMessage((CONTROL_CHANGE | channel) << 16 | CONTROL_BANK_CHANGE_LSB << 8 | bank & 0x7f);
                }
                player.sendMessage((PROGRAM_CHANGE | channel) << 16 | programme << 8);
            }
            catch(SoundPlayerException e)
            {
                throw new IllegalStateException("MIDIControl.setProgram: проигрыватель закрыт.");
            }
        }

        public boolean isBankQuerySupported() {
            return false;
        }

        public int longMidiEvent(byte[] src, int offset, int length) {
            return length; /* Малик Эмулятор не поддерживает отправку длинных событий MIDI */
        }

        public int getChannelVolume(int channel) {
            return -1; /* Малик Эмулятор не поддерживает чтение громкости канала */
        }

        public int[] getProgram(int channel) throws MediaException {
            throw new BankQueryNotSupportedException("MIDIControl.getProgram: получение банков не поддерживается.");
        }

        public int[] getProgramList(int bank) throws MediaException {
            throw new BankQueryNotSupportedException("MIDIControl.getProgramList: получение банков не поддерживается.");
        }

        public int[] getBankList(boolean custom) throws MediaException {
            throw new BankQueryNotSupportedException("MIDIControl.getBankList: получение банков не поддерживается.");
        }

        public String getProgramName(int bank, int programme) throws MediaException {
            throw new BankQueryNotSupportedException("MIDIControl.getProgramName: получение банков не поддерживается.");
        }

        public String getKeyName(int bank, int programme, int key) throws MediaException {
            throw new BankQueryNotSupportedException("MIDIControl.getKeyName: получение банков не поддерживается.");
        }

        public void setSystemPlayer(SyntheticSoundPlayer 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) {
            (SyntheticPlayer.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 Playback playback;
    private InputStream stream;
    private final SyntheticSoundDecoder decoder;

    public SyntheticPlayer(SyntheticPlayerDecoder decoder, InputStream stream, boolean closeNeed) {
        int len;
        Control[] controls;
        Playback playback;
        if(decoder == null)
        {
            throw new NullPointerException("SyntheticPlayer: аргумент decoder равен нулевой ссылке.");
        }
        if(stream == null)
        {
            throw new NullPointerException("SyntheticPlayer: аргумент 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 SyntheticSoundDecoder 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.synthesizerMessages = 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 lim;
                long[] messages;
                SyntheticSoundDecoder decoder;
                try
                {
                    (decoder = this.decoder).loadFromInputStream(stream);
                }
                finally
                {
                    if(closeNeed) stream.close();
                }
                if((playback.synthesizerMessages = messages = decoder.getMessages()) != null && (lim = messages.length - 1) >= 0) setDuration(1000L * (messages[lim] >> 24));
            }
            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(SyntheticSoundPlayer.open());
            }
            catch(SoundPlayerException e)
            {
                throw new MediaException(e.getMessage());
            }
        }
    }

    protected void playerStart() throws MediaException {
        long[] messages;
        Playback playback;
        SoundPlayer player;
        if((playback = this.playback) != null && (player = playback.systemPlayer) != null && (messages = playback.synthesizerMessages) != null)
        {
            if(playback.loopCountRemaining == 0) playback.loopCountRemaining = playback.loopCountSetted;
            try
            {
                if(playback.loaded)
                {
                    player.start();
                } else
                {
                    int pos;
                    player.loadBlock(messages, 0, messages.length);
                    playback.loaded = true;
                    if((pos = playback.position) > 0) player.setCurrentBlockPosition(pos);
                }
            }
            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[] messages;
        Playback playback;
        SoundPlayer player;
        if((playback = this.playback) != null && (player = playback.systemPlayer) != null && (messages = playback.synthesizerMessages) != null)
        {
            int posGuess;
            long guessTime;
            for(int pos1 = 0, pos2 = messages.length - 1; ; )
            {
                posGuess = (pos1 + pos2) >> 1;
                guessTime = 1000L * (messages[posGuess] >> 24);
                if(pos1 == pos2) break;
                if(mediaTime < guessTime)
                {
                    if((pos2 = posGuess - 1) < pos1) pos2 = pos1;
                    continue;
                }
                if(mediaTime > guessTime)
                {
                    if((pos1 = posGuess + 1) > pos2) pos1 = pos2;
                    continue;
                }
                break;
            }
            mediaTime = guessTime;
            if(playback.loaded)
            {
                try
                {
                    player.setCurrentBlockPosition(posGuess);
                }
                catch(SoundPlayerException e)
                {
                    throw new MediaException(e.getMessage());
                }
            } else
            {
                playback.position = posGuess;
            }
        }
        return mediaTime;
    }

    protected long playerGetMediaTime() {
        long[] messages;
        Playback playback;
        SoundPlayer player;
        if((playback = this.playback) != null && (player = playback.systemPlayer) != null && (messages = playback.synthesizerMessages) != null)
        {
            int pos;
            if(playback.loaded)
            {
                try
                {
                    pos = player.getCurrentBlockPosition();
                }
                catch(SoundPlayerException e)
                {
                    pos = playback.position;
                }
            } else
            {
                pos = playback.position;
            }
            return 1000L * (messages[pos] >> 24);
        }
        return TIME_UNKNOWN;
    }
}
