import java.applet.Applet;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.image.BufferedImage;
import java.awt.event.MouseEvent;
import java.util.logging.*;

import netscape.javascript.JSObject;
import com.videonext.mplayer.*;


public class MediaPlayer extends Applet {
	private static final long serialVersionUID = 6546613028913051661L;
	
	private Logger log = Logger.getAnonymousLogger();
	private com.videonext.mplayer.MediaPlayer player;	
	private CallbackListener callbackListener;  
	private Color bgColor = Color.DARK_GRAY;
	private long[] timestampsArray = new long[3];


	//
	// Applet life-cycle
	//	

	@Override	
	public void init()
	{
		// Setup logging
		log.setUseParentHandlers(false);		
		Formatter logFormatter = new LogFormatter(getName());
		Handler handler = new ConsoleHandler();
		handler.setLevel(Level.ALL);
		handler.setFormatter(logFormatter);
		log.addHandler(handler);
		
		String logFile = getParameter("LOGFILE"); 
		if (logFile != null && logFile.length() > 0) {
			logFile.replaceAll("%name", getName());
			try {
				handler = new FileHandler(logFile);
				handler.setLevel(Level.ALL);
				handler.setFormatter(logFormatter);
				log.addHandler(handler);
			} catch (Exception e) {}
		}
		
		try {
			log.setLevel(Level.parse(getParameter("LOGLEVEL")));
		} catch (Exception e) {
			log.setLevel(Level.INFO);
		}

		player = new com.videonext.mplayer.MediaPlayer(getName(), log);
		callbackListener = new CallbackListener();		
		player.setMediaPlayerListener(callbackListener);		
		
		// set chache size
		try {
			String cacheSizeStr = getParameter("CACHESIZE");
			if (cacheSizeStr == null) {
				player.setCacheSize(0);
			} else if (cacheSizeStr.indexOf("M") == -1) {
				log.warning("Incorrect \"CACHESIZE\" format. \"CACHESIZE\" must be integer value with 'M' postfix symbol!\n"); 
			} else {
				cacheSizeStr = cacheSizeStr.replace("M", "000000");
				Integer param = Integer.parseInt(cacheSizeStr);
				player.setCacheSize(param.intValue());
			}
		} catch (NumberFormatException e) {
			log.warning("Incorrect \"CACHESIZE\" format. \"CACHESIZE\" must be integer value with 'M' postfix symbol!\n"); 
		}

		// Set background color
		String bgcolorStr = getParameter("BGCOLOR");
		if (bgcolorStr != null) {
			try  {
				bgColor = Color.decode(bgcolorStr);
			} catch (NumberFormatException e) {
				log.warning("Incorrect \"BGCOLOR\" format.\n");
			}
		}
		setBackground(bgColor);
		player.setBackground(bgColor);
		
		setLayout(new GridLayout(1,1));
		add(player);
		doLayout();
		
		String playerVersion = getParameter("PLAYER_VERSION");
		if (playerVersion == null) {
			log.warning("Please specify \"PLAYER_VERSION\" param\n");
			return;
		}

		try {
			player.init(playerVersion);
		} catch (Exception e) {
			log.log(Level.SEVERE, "Failed to initilize MediaPlayer", e);
		}
	}
	
	@Override
	public void start()	{
		player.setOnHold(false);
	}
	
	
	@Override
	public void stop() {
		player.setOnHold(true);
	}	

	@Override
	public void destroy()
	{
		callbackListener.destroy();
		player.destroy();
	}
	
	@Override
	public String getName() {
		String name = getParameter("ID");
		return (name == null) ? super.getName() : name;
	}
	
	
	//
	// MediaPlayer applet API
	//
	
	// Invoked by JavaScript to prevent JaveScript callbacks
	// when HTML page is about to be destroyed (IE bug fix)
	public void beforeStop() {
		callbackListener.destroy();
	}
	
	public void setParameter(String param, String value) {
		player.setParameter(param, value);
	}
	
	public void sendCommand(String command) {
		player.sendCommand(command);
	}
	
	public void setSize(int width, int height) {
		super.setSize(width, height);
		validate();
	}
	
	public void printError(String msg) {
		player.printError(msg);
	}
	
	public void displaySign(int signId) {
		player.displaySign(signId);
	}
	
	public void showCenterSign(boolean showSign) {
		player.showCenterSign(showSign);
	}
	
	public String getStreamType() {
		return player.getStreamType();
	}
	
	public String getResultMsg() {
		return player.getResultMsg();
	}
	
	public int getResultCode() {
		return player.getResultCode();
	}
	
	// returns 1 when player is connecting, otherwise - 0
	public int isConnecting() {
		return player.isConnecting;
	}
	
	public long getCurrentTS() {
		return player.getCurrentTS();
	}
	
	public long[] getTimestamps() {
		timestampsArray[0] = player.getCurrentTS(); 
		timestampsArray[1] = player.getBufferStartTS();
        timestampsArray[2] = player.getBufferEndTS();
		return timestampsArray;
	}

	public void log(String msg)	{
		log.info(msg);
	}
	
	
	/**
	 * Helper class handles MediaPlayer callbacks and forwards them to corresponding
	 * JavaScript functions (if defined)
	 */
	protected class CallbackListener implements MediaPlayerListener {
		// JavaScript callbacks
		private static final int JSCALL_STATUSCHANGE = 0;
		private static final int JSCALL_NEWIMAGE = 1;
		private static final int JSCALL_BUFFERCHANGE = 2;
		private static final int JSCALL_IMAGECLICK = 3;     
		private static final int JSCALL_PLAYERREADY = 4;
                private static final int JSCALL_SNAPSHOTFINISH = 5;
		
		private boolean isDevEnvironment = false;
		private JSObject jsObject;
		private boolean isJSCallbackEnabled = false;	
		private String jsCallbacks[] = new String[6];
		private Long[] jsTimestampParam = new Long[6];
		private Long[] jsBufTimestampParam = new Long[4];


		public CallbackListener() {
			// Initialize JavaScript callbacks data
			isDevEnvironment = "sun.applet.AppletViewer".equals(System.getProperty("browser"));
			if (!isDevEnvironment) {
				try {
					jsObject = JSObject.getWindow(MediaPlayer.this);
					if (jsObject != null) {
						isJSCallbackEnabled = true;

						// Get JavaScript callback names
						jsCallbacks[JSCALL_STATUSCHANGE] = getParameter("onStatusChange");
						jsCallbacks[JSCALL_NEWIMAGE] = getParameter("onNewImage");
						jsCallbacks[JSCALL_BUFFERCHANGE] = getParameter("onBufferChange");
						jsCallbacks[JSCALL_IMAGECLICK] = getParameter("onImageClick");
						jsCallbacks[JSCALL_PLAYERREADY] = getParameter("onPlayerReady");
						jsCallbacks[JSCALL_SNAPSHOTFINISH] = getParameter("onSnapshotFinish");
					}
				} catch (netscape.javascript.JSException e) {
					log.log(Level.SEVERE, "Failed to get JSObject reference. JavaScript callbacks are disabled", e);
				}
			}
		}
		
		public void destroy() {
			isJSCallbackEnabled = false;
			jsObject = null;
		}
	
		//
		// MediaPlayerListener implementation
		//
		public void statusChanged(int status) {
			if (status == com.videonext.mplayer.MediaPlayer.STATUS_INITIALIZED) {
				callJSFunction(JSCALL_PLAYERREADY, new Integer[]{});
				if (isDevEnvironment) {
					dev_SelfTest();
				}
			} else {
				callJSFunction(JSCALL_STATUSCHANGE, new Integer[]{ status });
			}
		}
	
	
               public void newFrame(long frameTimestamp, long bufStartTimestamp, long bufEndTimestamp, BufferedImage im) {
			jsTimestampParam[0] = frameTimestamp / 1000;
			jsTimestampParam[1] = frameTimestamp % 1000 / 10;
			jsTimestampParam[2] = bufStartTimestamp / 1000;
			jsTimestampParam[3] = bufStartTimestamp % 1000 /10;
			jsTimestampParam[4] = bufEndTimestamp / 1000;
			jsTimestampParam[5] = bufEndTimestamp % 1000 / 10;
	
			callJSFunction(JSCALL_NEWIMAGE, jsTimestampParam);
		}
	
	
		public void bufferChanged(long bufStartTimestamp, long bufEndTimestamp) {
			jsBufTimestampParam[0] = bufStartTimestamp / 1000;
			jsBufTimestampParam[1] = bufStartTimestamp % 1000 /10;
			jsBufTimestampParam[2] = bufEndTimestamp / 1000;
			jsBufTimestampParam[3] = bufEndTimestamp % 1000 / 10;
	
			callJSFunction(JSCALL_BUFFERCHANGE, jsBufTimestampParam);
		}
	
	
		public void mouseClicked(MouseEvent e, boolean isPlayerActive) {
			if (jsCallbacks[JSCALL_IMAGECLICK] != null) {
				// Calling "onImageClick"
				Object[] params = new Object[3];
				params[0] = new Integer(e.getX());
				params[1] = new Integer(e.getY());
				params[2] = new Boolean(isPlayerActive);
				callJSFunction(JSCALL_IMAGECLICK, params);
			}
		}

		public void snapshotFinished() {
			callJSFunction(JSCALL_SNAPSHOTFINISH, new Integer[]{});
		}
	
		/**
		 * JS callback caller
		 * @param name JS function name
		 * @param params 
		 */
		private void callJSFunction(int name, Object[] params)
		{
			if (isJSCallbackEnabled) {
				String callbackName = jsCallbacks[name];
				if (callbackName != null) {
					try {
						jsObject.call(callbackName, params);
					} catch (Exception ex) {
						log.log(Level.SEVERE, "Failed to invoke JavaScript function \""+ callbackName + "\"", ex);
					}
				}
			}
		}
	}


	/**
	 * Start playback - used in development environment
	 */
	private void dev_SelfTest() {
		String selfTestURL = getParameter("SELF_TEST_URL");
		if (selfTestURL != null) {
			log.info("start selftesting");

			player.setParameter("videoURL", selfTestURL);
			player.setParameter("streamOverTCP", "on");
			player.setParameter("direction", "1");
			player.setParameter("streamSpeed", "1");
			player.setParameter("stepMode", "0");
			player.sendCommand("play");
			if (selfTestURL.indexOf("audio=") > 0) {			
				player.setParameter("mute", "off");
			}
			
		}		
	}
}
