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

import javax.microedition.media.*;
import malik.emulator.util.*;

public abstract class CustomPlayer extends ControlList implements Player
{
    private static final class EventThread extends Thread
    {
        private final RunnableQueue queue;

        public EventThread() {
            super("Обработчик событий от проигрывателей");
            this.queue = new RunnableQueue();
        }

        public void run() {
            for(RunnableQueue monitor = queue; ; )
            {
                Runnable event = null;
                synchronized(monitor)
                {
                    try
                    {
                        monitor.wait();
                    }
                    catch(InterruptedException e)
                    {
                        e.printRealStackTrace();
                    }
                    if(!monitor.isEmpty())
                    {
                        event = monitor.peekHeadRunnable();
                        ((Queue) monitor).removeHeadElement();
                    }
                }
                if(event != null) event.run();
            }
        }

        public void post(CustomPlayer player, String event, Object data) {
            RunnableQueue monitor;
            synchronized(monitor = queue)
            {
                monitor.addTailElement(player.new EventData(event, data));
                monitor.notify();
            }
        }
    }

    private static final EventThread EVENT;
    private static final Long MINUS_ONE;
    private static final Long ZERO;

    static {
        EventThread event;
        ((Thread) (event = new EventThread())).start();
        EVENT = event;
        MINUS_ONE = new Long(-1L);
        ZERO = new Long(0L);
    }

    private static Long toLongCover(long value) {
        return value == 0L ? ZERO : value == -1L ? MINUS_ONE : new Long(value);
    }

    private final class EventData extends Object implements Runnable
    {
        private final String event;
        private final Object data;

        public EventData(String event, Object data) {
            this.event = event;
            this.data = data;
        }

        public void run() {
            PlayerListener[] listeners;
            CustomPlayer player = CustomPlayer.this;
            String event = this.event;
            Object data = this.data;
            for(int i = (listeners = player.listeners) != null ? listeners.length : 0; i-- > 0; )
            {
                PlayerListener listener;
                if((listener = listeners[i]) != null)
                {
                    try
                    {
                        listener.playerUpdate(player, event, data);
                    }
                    catch(RuntimeException e)
                    {
                        e.printRealStackTrace();
                    }
                }
            }
            if(PlayerListener.CLOSED.equals(event)) player.listeners = null;
        }
    }

    private boolean realizing;
    private int state;
    PlayerListener[] listeners;
    private Long duration;
    protected TimeBase master;
    protected String contentType;
    protected final Object monitor;

    protected CustomPlayer() {
        this.state = UNREALIZED;
        this.duration = ZERO;
        this.master = Manager.getSystemTimeBase();
        this.monitor = new Object();
    }

    public final Control[] getControls() {
        int error = 0;
        Control[] result;
        synchronized(monitor)
        {
            label0:
            {
                int state;
                if((state = this.state) == UNREALIZED)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                if(state == CLOSED)
                {
                    error = 2;
                    result = null;
                    break label0;
                }
                result = super.getControls();
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalStateException("Player.getControls: проигрыватель не загружен данными.");
        case 2:
            throw new IllegalStateException("Player.getControls: проигрыватель закрыт.");
        }
        return result;
    }

    public final Control getControl(String controlTypeName) {
        int error = 0;
        Control result;
        synchronized(monitor)
        {
            label0:
            {
                int state;
                if((state = this.state) == UNREALIZED)
                {
                    error = 1;
                    result = null;
                    break label0;
                }
                if(state == CLOSED)
                {
                    error = 2;
                    result = null;
                    break label0;
                }
                if(controlTypeName == null)
                {
                    error = 3;
                    result = null;
                    break label0;
                }
                result = super.getControl(controlTypeName);
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalStateException("Player.getControl: проигрыватель не загружен данными.");
        case 2:
            throw new IllegalStateException("Player.getControl: проигрыватель закрыт.");
        case 3:
            throw new IllegalArgumentException("Player.getControl: аргумент controlTypeName равен нулевой ссылке.");
        }
        return result;
    }

    public final void close() {
        synchronized(monitor)
        {
            if(state != CLOSED)
            {
                playerClose();
                controls = null;
                state = CLOSED;
                duration = ZERO;
                master = null;
                contentType = null;
                postEvent(PlayerListener.CLOSED, null);
            }
        }
    }

    public final void deallocate() {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int state;
                if((state = this.state) == CLOSED)
                {
                    error = 1;
                    break label0;
                }
                if(state == UNREALIZED && realizing)
                {
                    playerUnrealize();
                    break label0;
                }
                if(state == STARTED)
                {
                    state = PREFETCHED;
                    postEvent(PlayerListener.STOPPED, toLongCover(playerGetMediaTime()));
                }
                if(state == PREFETCHED)
                {
                    playerUnprefetch();
                    this.state = REALIZED;
                    postEvent(PlayerListener.DEVICE_UNAVAILABLE, playerGetDeviceName());
                }
            }
        }
        if(error == 1)
        {
            throw new IllegalStateException("Player.deallocate: проигрыватель закрыт.");
        }
    }

    public final void realize() throws MediaException {
        boolean realized;
        int error = 0;
        Object monitor;
        MediaException exception;
        synchronized(monitor = this.monitor)
        {
            label0:
            {
                int state;
                if((state = this.state) == UNREALIZED && realizing)
                {
                    error = 1;
                    break label0;
                }
                if(state == CLOSED)
                {
                    error = 2;
                    break label0;
                }
                if(state == REALIZED || state == PREFETCHED || state == STARTED)
                {
                    error = 3;
                    break label0;
                }
                realizing = true;
            }
        }
        switch(error)
        {
        case 1:
            throw new MediaException("Player.realize: проигрыватель уже загружается данными.");
        case 2:
            throw new IllegalStateException("Player.realize: проигрыватель закрыт.");
        case 3:
            return;
        }
        realized = false;
        exception = null;
        try
        {
            playerRealize();
            realized = true;
        }
        catch(MediaException e)
        {
            exception = e;
        }
        catch(Exception e)
        {
        }
        synchronized(monitor)
        {
            if(state == UNREALIZED && realized) state = REALIZED;
            realizing = false;
        }
        if(exception != null)
        {
            throw exception;
        }
    }

    public final void prefetch() throws MediaException {
        switch(state)
        {
        case CLOSED:
            throw new IllegalStateException("Player.prefetch: проигрыватель закрыт.");
        case UNREALIZED:
            realize();
            if(state != REALIZED)
            {
                throw new MediaException("Player.prefetch: проигрыватель не может сейчас получить доступ к ресурсам.");
            }
            /* fall through */
        }
        synchronized(monitor)
        {
            if(state == REALIZED)
            {
                playerPrefetch();
                state = PREFETCHED;
                postEvent(PlayerListener.DEVICE_AVAILABLE, playerGetDeviceName());
            }
        }
    }

    public final void start() throws MediaException {
        switch(state)
        {
        case CLOSED:
            throw new IllegalStateException("Player.start: проигрыватель закрыт.");
        case UNREALIZED:
            realize();
            if(state != REALIZED)
            {
                throw new MediaException("Player.start: проигрыватель не может сейчас начать проигрывание данных.");
            }
            /* fall through */
        case REALIZED:
            prefetch();
            if(state != PREFETCHED)
            {
                throw new MediaException("Player.start: проигрыватель не может сейчас начать проигрывание данных.");
            }
            /* fall through */
        }
        synchronized(monitor)
        {
            if(state == PREFETCHED)
            {
                long mediaTime = playerGetMediaTime();
                playerStart();
                state = STARTED;
                postEvent(PlayerListener.STARTED, toLongCover(mediaTime));
            }
        }
    }

    public final void stop() throws MediaException {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int state;
                if((state = this.state) == CLOSED)
                {
                    error = 1;
                    break label0;
                }
                if(state == STARTED)
                {
                    playerStop();
                    this.state = PREFETCHED;
                    postEvent(PlayerListener.STOPPED, toLongCover(playerGetMediaTime()));
                }
            }
        }
        if(error == 1)
        {
            throw new IllegalStateException("Player.stop: проигрыватель закрыт.");
        }
    }

    public final void addPlayerListener(PlayerListener listener) {
        if(state == CLOSED)
        {
            throw new IllegalStateException("Player.addPlayerListener: проигрыватель закрыт.");
        }
        if(listener == null) return;
        synchronized(monitor)
        {
            int len;
            PlayerListener[] listeners;
            if((listeners = this.listeners) == null) listeners = this.listeners = new PlayerListener[3];
            if(Array.findf(listeners, 0, listener) >= (len = listeners.length))
            {
                int index;
                if((index = Array.findf(listeners, 0, null)) >= len)
                {
                    Array.copy(listeners, 0, listeners = new PlayerListener[(len << 1) + 1], 0, len);
                    this.listeners = listeners;
                }
                listeners[index] = listener;
            }
        }
    }

    public final void removePlayerListener(PlayerListener listener) {
        if(state == CLOSED)
        {
            throw new IllegalStateException("Player.removePlayerListener: проигрыватель закрыт.");
        }
        if(listener == null) return;
        synchronized(monitor)
        {
            int index;
            PlayerListener[] listeners;
            if((listeners = this.listeners) != null && (index = Array.findf(listeners, 0, listener)) < listeners.length) listeners[index] = null;
        }
    }

    public final void setLoopCount(int loopCount) {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int state;
                if((state = this.state) == STARTED)
                {
                    error = 1;
                    break label0;
                }
                if(state == CLOSED)
                {
                    error = 2;
                    break label0;
                }
                if(loopCount == 0)
                {
                    error = 3;
                    break label0;
                }
                playerSetLoopCount(loopCount);
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalStateException("Player.setLoopCount: проигрыватель занят.");
        case 2:
            throw new IllegalStateException("Player.setLoopCount: проигрыватель закрыт.");
        case 3:
            throw new IllegalArgumentException("Player.setLoopCount: аргумент loopCount не может быть равен нулю.");
        }
    }

    public final void setTimeBase(TimeBase master) throws MediaException {
        int error = 0;
        synchronized(monitor)
        {
            label0:
            {
                int state;
                if((state = this.state) == UNREALIZED)
                {
                    error = 1;
                    break label0;
                }
                if(state == STARTED)
                {
                    error = 2;
                    break label0;
                }
                if(state == CLOSED)
                {
                    error = 3;
                    break label0;
                }
                playerSetTimeBase(master);
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalStateException("Player.setTimeBase: проигрыватель не загружен данными.");
        case 2:
            throw new IllegalStateException("Player.setTimeBase: проигрыватель занят.");
        case 3:
            throw new IllegalStateException("Player.setTimeBase: проигрыватель закрыт.");
        }
    }

    public final int getState() {
        return state;
    }

    public final long setMediaTime(long mediaTime) throws MediaException {
        int error = 0;
        long result;
        synchronized(monitor)
        {
            label0:
            {
                int state;
                if((state = this.state) == UNREALIZED)
                {
                    error = 1;
                    result = 0L;
                    break label0;
                }
                if(state == CLOSED)
                {
                    error = 2;
                    result = 0L;
                    break label0;
                }
                result = playerSetMediaTime(mediaTime);
            }
        }
        switch(error)
        {
        case 1:
            throw new IllegalStateException("Player.setMediaTime: проигрыватель не загружен данными.");
        case 2:
            throw new IllegalStateException("Player.setMediaTime: проигрыватель закрыт.");
        }
        return result;
    }

    public final long getMediaTime() {
        int error = 0;
        long result;
        synchronized(monitor)
        {
            label0:
            {
                if(state == CLOSED)
                {
                    error = 1;
                    result = 0L;
                    break label0;
                }
                result = playerGetMediaTime();
            }
        }
        if(error == 1)
        {
            throw new IllegalStateException("Player.getMediaTime: проигрыватель закрыт.");
        }
        return result;
    }

    public final long getDuration() {
        if(state == CLOSED)
        {
            throw new IllegalStateException("Player.getDuration: проигрыватель закрыт.");
        }
        return duration.longValue();
    }

    public final TimeBase getTimeBase() {
        switch(state)
        {
        case CLOSED:
            throw new IllegalStateException("Player.getTimeBase: проигрыватель закрыт.");
        case UNREALIZED:
            throw new IllegalStateException("Player.getTimeBase: проигрыватель не загружен данными.");
        }
        return master;
    }

    public final String getContentType() {
        switch(state)
        {
        case CLOSED:
            throw new IllegalStateException("Player.getContentType: проигрыватель закрыт.");
        case UNREALIZED:
            throw new IllegalStateException("Player.getContentType: проигрыватель не загружен данными.");
        }
        return contentType;
    }

    public final boolean isRealizing() {
        return realizing;
    }

    protected abstract void playerUnrealize();

    protected abstract void playerUnprefetch();

    protected abstract void playerRealize() throws MediaException, InterruptedException;

    protected abstract void playerPrefetch() throws MediaException;

    protected abstract void playerStart() throws MediaException;

    protected abstract void playerStop() throws MediaException;

    protected abstract void playerSetLoopCount(int loopCount);

    protected abstract long playerSetMediaTime(long mediaTime) throws MediaException;

    protected abstract long playerGetMediaTime();

    protected void postEvent(String event, Object data) {
        EVENT.post(this, event, data);
    }

    protected void playerClose() {
    }

    protected void playerRestart() {
        postEvent(PlayerListener.END_OF_MEDIA, duration);
        postEvent(PlayerListener.STARTED, ZERO);
    }

    protected void playerEndOfMedia() {
        state = PREFETCHED;
        postEvent(PlayerListener.END_OF_MEDIA, duration);
    }

    protected void playerSetTimeBase(TimeBase master) throws MediaException {
        if(master != Manager.getSystemTimeBase())
        {
            throw new MediaException("Player.setTimeBase: установка другой базы времени не поддерживается.");
        }
    }

    protected String playerGetDeviceName() {
        return "Системный проигрыватель";
    }

    protected final void setDuration(long duration) {
        Long newDuration;
        this.duration = newDuration = toLongCover(duration);
        postEvent(PlayerListener.DURATION_UPDATED, newDuration);
    }
}
