package com.one;

import javax.microedition.media.*;
import javax.microedition.media.control.*;
import java.io.*;
import java.util.*;
import com.ibxm.*;

public class ModulePlayer implements Runnable, Player, PlayerListener
{
	protected static final String CONTENT = "audio/x-wav";	
	protected static final Random rnd = new Random();

	public static final int[] SAMPLE_RATES = new int[] { 8000, 11025, 16000, 22050, 32000, 44100 };
	
	protected IBXM ibxm;
	protected int samplerate;
	protected int bufseconds;
	protected int bufsamples;
	protected int bufdispersion;
	protected int totalsamples;
	protected int samplesleft;
	
	protected Player currentPlayer;
	protected Player playerA, playerB;
	protected byte[] bufferA, bufferB;
	protected Thread t;
	protected long prevtime, totaltime;
	protected Vector listeners;
	protected int state;
	protected int loopcount;
	protected VolumeControl volumecontrol;
	protected int volumelevel;
	protected boolean volumemute;
	
	public ModulePlayer(InputStream input, int samplerateindex, int quality, int bufseconds) throws IOException
	{
		this.samplerate = SAMPLE_RATES[samplerateindex];
		this.bufseconds = bufseconds;
		
		ibxm = new IBXM(input, samplerate, quality);
		
		totalsamples = ibxm.calculate_song_duration();
		totaltime = (long)totalsamples * 1000000L / (long)samplerate;
		
		listeners = new Vector();
		
		state = UNREALIZED;

		volumelevel = 100;
		volumemute = false;
	}
	
	public void addPlayerListener(PlayerListener listener)
	{
		if(listener != null && !listeners.contains(listener))
		{
			listeners.addElement(listener);
		}
	}
	
	public void close()
	{
		deallocate();
		
		state = Player.CLOSED;
		postEvent(PlayerListener.CLOSED, null);
		
		bufferA = null;
		bufferB = null;
		
		ibxm = null;

		System.gc();
	}
	
	public void deallocate()
	{
		if(state == Player.CLOSED)
		{
			throw new IllegalStateException("Player is closed");
		}
		
		try
		{
			stop();
		}
		catch(MediaException me)
		{
		}
		
		if(state == PREFETCHED)
		{
			if(t != null && t.isAlive())
			{
				try
				{
					t.join();
				}
				catch(InterruptedException ie)
				{
				}
			}
			
			currentPlayer = null;
			updateVolumeControl();
			
			if(playerA != null)
			{
				playerA.close();
				playerA = null;
			}
			
			if(playerB != null)
			{
				playerB.close();
				playerB = null;
			}
			
			state = REALIZED;
		}
	}
	
	public String getContentType()
	{
		return CONTENT;
	}
	
	public long getDuration()
	{
		if(state == Player.CLOSED)
		{
			throw new IllegalStateException("Player is closed");
		}
		
		return totaltime;
	}
	
	public long getMediaTime()
	{
		if(state == Player.CLOSED)
		{
			throw new IllegalStateException("Player is closed");
		}
		
		if(currentPlayer != null)
		{
			return prevtime + currentPlayer.getMediaTime();
		}
		else
		{
			return prevtime;
		}
	}
	
	public int getState()
	{
		return state;
	}
	
	public TimeBase getTimeBase()
	{
		return Manager.getSystemTimeBase();
	}
	
	public void prefetch() throws MediaException
	{
		realize();
		
		if(state == REALIZED)
		{
			startThread();
			
			if(t.isAlive())
			{
				try
				{
					t.join();
				}
				catch(InterruptedException ie)
				{
				}
			}
			
			currentPlayer = playerA;
			updateVolumeControl();

			startThread();

			if(t.isAlive())
			{
				try
				{
					t.join();
				}
				catch(InterruptedException ie)
				{
				}
			}

			state = PREFETCHED;
		}
	}
	
	public void realize() throws MediaException
	{
		if(state == Player.CLOSED)
		{
			throw new IllegalStateException("Player is closed");
		}
		
		if(state == UNREALIZED)
		{
			ibxm.seek(0);
			samplesleft = totalsamples;
			prevtime = 0;
			
			int bufsize;
			
			while(true)
			{
				try
				{
					if(bufseconds <= 0)
					{
						bufseconds = (int)(totaltime / 2000000L) + 1;
					}
					
					bufsamples = bufseconds * samplerate;
					bufdispersion = bufsamples / 5;
					bufsize = (bufsamples + bufdispersion) * 2 * 2 + WavHeader.SIZE;
					
					bufferA = new byte[bufsize];
					bufferB = new byte[bufsize];
					
					break;
				}
				catch(Throwable e)
				{
					bufferA = null;
					bufferB = null;
					System.gc();
					
					if(--bufseconds <= 0)
					{
						throw new MediaException("Consider this as \"OutOfMemoryError\"");
					}
				}
			}
			
			state = REALIZED;
		}
	}
	
	public void removePlayerListener(PlayerListener listener)
	{
		if(listener != null)
		{
			listeners.removeElement(listener);
		}
	}
	
	public void setLoopCount(int count)
	{
		loopcount = count;
	}
	
	public long setMediaTime(long now) throws MediaException
	{
		if(state == Player.CLOSED)
		{
			throw new IllegalStateException("Player is closed");
		}

		if(now < 0)
		{
			now = 0;
		}
		else if(now > totaltime)
		{
			now = totaltime;
		}
		
		if(currentPlayer != null && now >= prevtime && now <= prevtime + currentPlayer.getDuration())
		{
			currentPlayer.setMediaTime(now - prevtime);
			return getMediaTime();
		}
		
		deallocate();
		
		int framepos = (int)((long)samplerate * now / 1000000L);
		
		ibxm.seek(framepos);
		samplesleft = totalsamples - framepos;
		prevtime = now;
		
		return getMediaTime();
	}
	
	public void setTimeBase(TimeBase master)
	{
	}
	
	public void start() throws MediaException
	{
		prefetch();
		
		if(state == PREFETCHED)
		{
			if(currentPlayer != null)
			{
				currentPlayer.start();

				state = Player.STARTED;
				postEvent(PlayerListener.STARTED, null);
			}
		}
	}
	
	public void stop() throws MediaException
	{
		if(state == Player.CLOSED)
		{
			throw new IllegalStateException("Player is closed");
		}
		
		if(state == Player.STARTED)
		{
			if(currentPlayer != null)
			{
				currentPlayer.stop();
			}
			
			state = PREFETCHED;
			postEvent(STOPPED, null);
		}
	}
	
	public String getSongTitle()
	{
		return ibxm.getSongTitle();
	}
	
	protected void updateVolumeControl()
	{
		if(currentPlayer != null)
		{
			volumecontrol = (VolumeControl)currentPlayer.getControl("VolumeControl");
			
			volumecontrol.setLevel(volumelevel);
			volumecontrol.setMute(volumemute);
		}
		else
		{
			volumecontrol = null;
		}
	}

	public void setVolumeLevel(int level)
	{
		volumelevel = level;

		if(volumecontrol != null)
		{
			volumecontrol.setLevel(volumelevel);
		}
	}

	public void setVolumeMute(boolean mute)
	{
		volumemute = mute;

		if(volumecontrol != null)
		{
			volumecontrol.setMute(volumemute);
		}
	}
	
	public void playerUpdate(Player player, String event, Object data)
	{
		if(event.equals(PlayerListener.STARTED))
		{
			if(state != Player.STARTED)
			{
				try
				{
					player.stop();
				}
				catch(MediaException me)
				{
				}
			}
		}
		else if(event.equals(END_OF_MEDIA))
		{
			try
			{
				if(t.isAlive())
				{
					try
					{
						t.join();
					}
					catch(InterruptedException ie)
					{
					}
				}

				if(player == playerA)
				{
					prevtime += player.getDuration();

					currentPlayer = playerB;
					updateVolumeControl();
					
					if(playerB != null)
					{
						if(state == Player.STARTED)
						{
							playerB.start();
						}
					}
					else
					{
						postEvent(END_OF_MEDIA, null);

						if(state != Player.CLOSED)
						{
							if(loopcount < 0)
							{
								setMediaTime(0);
							}
							else if(loopcount > 0)
							{
								setMediaTime(0);
								loopcount--;
							}
						}
					}

					if(playerA != null)
					{
						playerA.close();
						playerA = null;
					}
				}
				else if(player == playerB)
				{
					prevtime += player.getDuration();

					currentPlayer = playerA;
					updateVolumeControl();
					
					if(playerA != null)
					{
						if(state == Player.STARTED)
						{
							playerA.start();
						}
					}
					else
					{
						postEvent(END_OF_MEDIA, null);
						
						if(state != Player.CLOSED)
						{
							if(loopcount < 0)
							{
								setMediaTime(0);
							}
							else if(loopcount > 0)
							{
								setMediaTime(0);
								loopcount--;
							}
						}
					}

					if(playerB != null)
					{
						playerB.close();
						playerB = null;
					}
				}
				else
				{
					//deallocate();
					return;
				}
				
				startThread();
			}
			catch(Exception e)
			{
				ErrScreen.showErrMsg(97, e);
				deallocate();
			}
		}
	}

	protected void startThread()
	{
		t = new Thread(this, "ModulePlayer/Bufferization");
		t.setPriority(Thread.MIN_PRIORITY);
		t.start();
	}
	
	public void run()
	{
		try
		{
			if(samplesleft <= 0)
			{
				return;
			}
			
			Thread.yield();
			
			int numsamples;
			
			if(samplesleft > bufsamples)
			{
				if(bufdispersion > 0)
				{
					numsamples = bufsamples + rnd.nextInt() % bufdispersion;
				}
				else
				{
					numsamples = bufsamples;
				}
			}
			else
			{
				numsamples = samplesleft;
			}
			
			samplesleft -= numsamples;
			
			if(playerA == null)
			{
				WavHeader.writeHeader(bufferA, 2, samplerate, 2, numsamples);
				ibxm.get_audio(bufferA, WavHeader.SIZE, numsamples);
				
				playerA = Manager.createPlayer(new ByteArrayInputStream(bufferA), CONTENT);
				playerA.realize();
				playerA.prefetch();
				playerA.addPlayerListener(this);
			}
			else if(playerB == null)
			{
				WavHeader.writeHeader(bufferB, 2, samplerate, 2, numsamples);
				ibxm.get_audio(bufferB, WavHeader.SIZE, numsamples);
				
				playerB = Manager.createPlayer(new ByteArrayInputStream(bufferB), CONTENT);
				playerB.realize();
				playerB.prefetch();
				playerB.addPlayerListener(this);
			}
			else
			{
				deallocate();
			}
		}
		catch(Exception e)
		{
			ErrScreen.showErrMsg(98, e);
		}
	}
	
	protected void postEvent(String event, Object data)
	{
		for(int i = 0; i < listeners.size(); i++)
		{
			((PlayerListener)listeners.elementAt(i)).playerUpdate(this, event, data);
		}
	}
	
	public Control getControl(String controlType)
	{
		return null;
	}
	
	public Control[] getControls()
	{
		return null;
	}
}
