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

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

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

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

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


package com.nokia.mid.sound;

import java.io.*;
import java.lang.ref.*;
import javax.microedition.media.*;
import javax.microedition.media.control.*;

public class Sound extends Object
{
	private static final class Helper extends WeakReference
			implements PlayerListener, SoundListener
	{
		Helper(Sound thisSound)
		{
			super(thisSound);
		}

		public void playerUpdate(Player player, String event, Object data)
		{
			Sound thisSound;
			SoundListener listener;
			if((thisSound = (Sound) get()) == null)
			{
				return;
			}
			if(STARTED.equals(event) || STOPPED.equals(event) || CLOSED.equals(event))
			{
				if((listener = thisSound.listener) != null)
				{
					listener.soundStateChanged(thisSound, thisSound.state);
				}
				return;
			}
			if(END_OF_MEDIA.equals(event))
			{
				thisSound.state = SOUND_STOPPED;
				if((listener = thisSound.listener) != null)
				{
					listener.soundStateChanged(thisSound, SOUND_STOPPED);
				}
			}
		}

		public void soundStateChanged(Sound sound, int event)
		{
			Object thisSound;
			if((thisSound = get()) != null && thisSound instanceof SoundListener)
			{
				((SoundListener) thisSound).soundStateChanged(sound, event);
			}
		}
	}

	public static final int FORMAT_TONE = 1;
	public static final int FORMAT_WAV = 5;
	public static final int SOUND_PLAYING = 0;
	public static final int SOUND_STOPPED = 1;
	public static final int SOUND_UNINITIALIZED = 3;

	public static int getConcurrentSoundCount(int type)
	{
		return 1;
	}

	public static int[] getSupportedFormats()
	{
		return new int[] {
				FORMAT_TONE, FORMAT_WAV
		};
	}


	private int state;
	private Helper helper;
	private Player implement;
	private VolumeControl volume;
	private SoundListener listener;

	public Sound(int frequency, long duration)
	{
		this.state = SOUND_UNINITIALIZED;
		this.helper = new Helper(this);
		recreate(frequency, duration);
	}

	public Sound(byte[] src, int type)
	{
		this.state = SOUND_UNINITIALIZED;
		this.helper = new Helper(this);
		recreate(src, type);
	}

	public void init(int frequency, long duration)
	{
		synchronized(helper)
		{
			recreate(frequency, duration);
		}
	}

	public void init(byte[] src, int type)
	{
		if(src == null)
		{
			throw new NullPointerException("Sound: " +
					"параметр src равен нулевой ссылке.");
		}
		if(type == FORMAT_TONE)
		{
			throw new IllegalArgumentException("Sound: " +
					"формат данных FORMAT_TONE не поддерживается этой реализацией. " +
					"Этот формат устарел и в настоящее время не используется.");
		}
		synchronized(helper)
		{
			recreate(src, type);
		}
	}

	public void release()
	{
		Player implement;
		synchronized(helper)
		{
			if((implement = this.implement) != null)
			{
				implement.close();
				this.implement = null;
				this.volume = null;
				this.state = SOUND_UNINITIALIZED;
			}
		}
	}

	public void resume()
	{
		Player implement;
		try
		{
			if((implement = this.implement) != null)
			{
				implement.start();
				this.state = SOUND_PLAYING;
			}
		}
		catch(MediaException e)
		{
			e.printRealStackTrace();
		}
	}

	public void play(int loopCount)
	{
		Player implement;
		if(loopCount < 0)
		{
			throw new IllegalArgumentException("Sound.play: " +
					"недопустимое значение параметра loop.");
		}
		if(loopCount >= 0xff)
		{
			return;
		}
		if(loopCount == 0)
		{
			loopCount = -1;
		}
		synchronized(helper)
		{
			try
			{
				if((implement = this.implement) != null)
				{
					implement.stop();
					implement.setLoopCount(loopCount);
					implement.start();
					this.state = SOUND_PLAYING;
				}
			}
			catch(MediaException e)
			{
				e.printRealStackTrace();
			}
		}
	}

	public void stop()
	{
		Player implement;
		try
		{
			if((implement = this.implement) != null)
			{
				implement.stop();
				this.state = SOUND_STOPPED;
			}
		}
		catch(MediaException e)
		{
			e.printRealStackTrace();
		}
	}

	public void setSoundListener(SoundListener listener)
	{
		this.listener = listener == this ? helper : listener;
	}

	public void setGain(int level)
	{
		VolumeControl volume;
		level = level < 0 ? 0 : (level > 0xff ? 0xff : level);
		if((volume = this.volume) != null)
		{
			volume.setLevel(level * 100 / 255);
		}
	}

	public int getGain()
	{
		VolumeControl volume;
		return (volume = this.volume) != null ? volume.getLevel() * 255 / 100 : -1;
	}

	public int getState()
	{
		return state;
	}

	private void recreate(int frequency, long duration)
	{
		System.out.println("Метод com.nokia.mid.sound.Sound.init(int, long) не реализован.");
	}

	private void recreate(byte[] src, int type)
	{
		Player implement;
		VolumeControl volume;
		if(src == null)
		{
			throw new NullPointerException("Sound: " +
					"параметр src равен нулевой ссылке.");
		}
		switch(type)
		{
		case FORMAT_WAV:
			try
			{
				if((implement = this.implement) != null)
				{
					implement.close();
				}
				(implement = Manager.createPlayer(new ByteArrayInputStream(src), "audio/x-wav")).
						addPlayerListener(helper);
				implement.prefetch();
				volume = (VolumeControl) implement.getControl("VolumeControl");
				this.implement = implement;
				this.volume = volume;
				this.state = SOUND_STOPPED;
			}
			catch(Exception e)
			{
				throw new IllegalArgumentException("Sound: " +
						"не удалось декодировать данные согласно формату данных FORMAT_WAV.");
			}
			break;
		case FORMAT_TONE:
			throw new IllegalArgumentException("Sound: " +
					"формат данных FORMAT_TONE не поддерживается этой реализацией. " +
					"Этот формат устарел и в настоящее время не используется.");
		default:
			throw new IllegalArgumentException("Sound: " +
					"недопустимое значение параметра type.");
		}
	}
}
