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

import javax.microedition.media.*;

public abstract class CustomPlayer extends Object
		implements Control, Controllable, Player
{
	private static final PlayerEventsListenerThread EVENTS_LISTENER;
	private static final TimeBase SYSTEM_TIME_BASE;

	static
	{
		PlayerEventsListenerThread t;
		EVENTS_LISTENER = t = new PlayerEventsListenerThread();
		SYSTEM_TIME_BASE = new SystemTimeBase();
		t.start();
	}

	public static final TimeBase getSystemTimeBase()
	{
		return SYSTEM_TIME_BASE;
	}


	final class Event extends Object
	{
		private String event;
		private Object data;

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

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

	protected int state;
	protected Object lock;
	private PlayerListener[] listeners;

	protected CustomPlayer()
	{
		this.state = UNREALIZED;
		this.lock = new Object();
	}

	public abstract void close();

	public abstract void start()
			throws MediaException;

	public abstract void stop()
			throws MediaException;

	public abstract void setLoopCount(int loopCount);

	public abstract long setMediaTime(long time)
			throws MediaException;

	public abstract long getMediaTime();

	public abstract long getDuration();

	public void realize()
			throws MediaException
	{
	}

	public void prefetch()
			throws MediaException
	{
	}

	public void deallocate()
	{
		switch(state)
		{
		default:
			return;
		case CLOSED:
			throw new IllegalStateException("Player.deallocate: " +
					"проигрыватель закрыт.");
		case UNREALIZED:
			unrealize();
			return;
		case PREFETCHED:
		case STARTED:
			unprefetch();
			return;
		}
	}

	public void setTimeBase(TimeBase master)
			throws MediaException
	{
		if(master != null && !(master instanceof SystemTimeBase))
		{
			throw new MediaException("Player.setTimeBase: " +
					"установка других способов исчисления времени не поддерживается.");
		}
	}

	public TimeBase getTimeBase()
	{
		return SYSTEM_TIME_BASE;
	}

	public final Control[] getControls()
	{
		switch(state)
		{
		case UNREALIZED:
			deliverEvent(PlayerListener.ERROR, "Player.getControls: " +
					"невозможно получить «рычаги» проигрывателя.");
			/* fall through */
		case CLOSED:
			throw new IllegalStateException("Player.getControls: " +
					"невозможно получить «рычаги» проигрывателя.");
		default:
			return getPlayerControls();
		}
	}

	public final Control getControl(String type)
	{
		switch(state)
		{
		case UNREALIZED:
			deliverEvent(PlayerListener.ERROR, "Player.getControl: " +
					"невозможно получить «рычаги» проигрывателя.");
			/* fall through */
		case CLOSED:
			throw new IllegalStateException("Player.getControl: " +
					"невозможно получить «рычаги» проигрывателя.");
		default:
			return getPlayerControl(type);
		}
	}

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

	public final void removePlayerListener(PlayerListener listener)
	{
		int i;
		PlayerListener[] listeners;
		if(state == CLOSED)
		{
			throw new IllegalStateException("Player.removePlayerListener: " +
					"проигрыватель закрыт.");
		}
		if(listener == null)
		{
			return;
		}
		synchronized(lock)
		{
			if((listeners = this.listeners) != null && (i = indexOfListener(listener)) >= 0)
			{
				listeners[i] = null;
			}
		}
	}

	public final int getState()
	{
		return state;
	}

	public final String getContentType()
	{
		switch(state)
		{
		case UNREALIZED:
			deliverEvent(PlayerListener.ERROR, "Player.getContentType: " +
					"невозможно получить тип содержимого проигрывателя.");
			/* fall through */
		case CLOSED:
			throw new IllegalStateException("Player.getContentType: " +
					"невозможно получить тип содержимого проигрывателя.");
		default:
			return getPlayerContentType();
		}
	}

	protected abstract String getPlayerContentType();

	protected void unrealize()
	{
	}

	protected void unprefetch()
	{
		state = REALIZED;
	}

	protected Control[] getPlayerControls()
	{
		return new Control[] {
				this
		};
	}

	protected Control getPlayerControl(String type)
	{
		Class controlClass;
		Control result;
		if(type.indexOf('.') < 0)
		{
			type = "javax.microedition.media.control.".concat(type);
		}
		try
		{
			result = Class.forName("javax.microedition.media.Control").
					isAssignableFrom(controlClass = Class.forName(type)) &&
					controlClass.isAssignableFrom(getClass()) ? this : null;
		}
		catch(ClassNotFoundException e)
		{
			e.printRealStackTrace();
			result = null;
		}
		return result;
	}

	protected final void deliverEvent(String event, Object data)
	{
		if(listeners != null)
		{
			EVENTS_LISTENER.pushEvent(this, event, data);
		}
	}

	private int indexOfListener(PlayerListener listener)
	{
		int i;
		PlayerListener[] listeners;
		for(i = (listeners = this.listeners).length; i-- > 0; )
		{
			if(listeners[i] == listener)
			{
				return i;
			}
		}
		return -1;
	}
}
