package com.videonext.mplayer;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import com.videonext.mplayer.PlayerGroup.ContextState;
import com.videonext.mplayer.api.ControllerEvent;
import com.videonext.mplayer.api.IControllerListener;
import com.videonext.mplayer.api.IPlayer;
import com.videonext.mplayer.api.events.NewFrameEvent;

public class SinglePlayerSyncronizer implements IControllerListener{
	private final long m_syncPeriod = 3000; 
	private final long m_exceptableError = 250;
	private Boolean m_isJustJumped = false;
	
	public enum SyncState {
		Waiting,
		Synchronizing,
		Playing
	}
	private SyncState m_syncState = SyncState.Playing; 
	private void setState(SyncState newState)
	{
		if(m_syncState == newState) return;
		SyncState oldState = m_syncState;
		m_syncState = newState;
		m_propertyChangeSupport.firePropertyChange("SyncState", oldState, newState);
	}
	public void switchToPlaying()
	{ setState(SyncState.Playing); }
	public SyncState getState() { return m_syncState; }
	
	private IPlayer m_player;
	private float m_speed = 1;	 
	public IPlayer getPlayer() {return m_player;}
	public void setSpeed(float value) 
	{ 
		if(Math.abs(value - m_speed) < 0.0001) return;
		m_speed = value; 
		m_player.changeSpeed(value); 
		m_startCountTime = 0;
		m_realStartCountTime = 0;
	}
	public float getSpeed() { return m_speed; }
	private float m_empiricSpeed = 1;
	public float getEmpiricSpeed() { return m_empiricSpeed; }
	private long m_realStartCountTime = 0;
	private long m_startCountTime = 0;	 
	private long m_playingTime = 0;
	public long getPlayingTime() { return m_playingTime; }
	private PlayerGroup m_group;
	public PlayerGroup getGroup() { return m_group; }
	public void resetEmpiricSpeed()
	{ m_startCountTime = 0; }
	private long m_lastSyncTime = 0;
	private Timer m_awaker;
	private long m_frameIncomeTime = new Date().getTime();
	private Boolean m_needJumpToPlayTime = false;
	public void setJumpToPlayTime()
	{
		m_needJumpToPlayTime = true;
	}
	
	public void jumpTo(Date date)
	{		
		synchronized (m_player) {
			m_player.jumpTo(date);
			m_isJustJumped = true;
		}
	}
	
	private float adjustSpeed()
	{
		if(m_playingTime == 0) return m_group.getMetronome().getSpeed();
		long playingTimeFinishingSync = m_group.getPlayingTime() + (long)(m_syncPeriod * m_group.getMetronome().getSpeed());
		long syncTime = playingTimeFinishingSync - m_playingTime;
		float newPlayerSpeed = ((float)syncTime) / ((float)m_syncPeriod);
		if((newPlayerSpeed < 0 && m_group.getMetronome().getDirection() == Player.Forward_Direction) ||
				(newPlayerSpeed > 0 && m_group.getMetronome().getDirection()  == Player.Backward_Direction))
		{
			newPlayerSpeed = m_group.getMetronome().getSpeed() / 10;
		}
		return newPlayerSpeed;
	}
	
	private void sync()
	{
		synchronized(m_player) {
			m_lastSyncTime = new Date().getTime();
			long timeDiff = Math.abs(m_playingTime - m_group.getPlayingTime());
			if(timeDiff < m_exceptableError)
			{
				setState(SyncState.Playing);
				setSpeed(m_group.getMetronome().getSpeed());				
				return;
			}
			if(timeDiff > m_syncPeriod * 4)
			{
				m_startCountTime = 0;
				m_realStartCountTime = 0;
				setSpeed(m_group.getMetronome().getSpeed());
				setState(SyncState.Playing);
				jumpTo(new Date(m_group.getPlayingTime()));
				
				return;
			}
			float newSpeed = adjustSpeed();
			setSpeed(newSpeed);
			setState(SyncState.Synchronizing);
		}
	}
	
	
	public SinglePlayerSyncronizer(IPlayer player, PlayerGroup group) {
		m_player = player;
		m_player.addControllerListener(this);
		m_group = group;
		m_awaker = new Timer();
		m_awaker.schedule(new TimerTask() {
			
			@Override
			public void run() {
				synchronized (m_player) {
					if(m_group.getContext().getState() == ContextState.Live) return;
					if(m_syncState == SyncState.Waiting && 
							((m_playingTime - m_group.getPlayingTime()) * m_group.getMetronome().getDirection() < m_syncPeriod))
						m_player.play(m_group.getMetronome().getDirection());
					if(new Date().getTime() - m_frameIncomeTime > m_syncPeriod && (m_player.getState() == Player.Playing_Buffer || m_player.getState() == Player.Playing_Server))
					{
						System.out.println("Restarting context");
						m_group.getContext().Apply(m_player);
					}
				}
				
			}
		}, m_syncPeriod, m_syncPeriod);
	}
	
	public void Dispose()
	{
		m_player.removeControllerListener(this);
	}
	@Override
	public void controllerUpdate(ControllerEvent event) {
		if(!(event instanceof NewFrameEvent)) return;
		if(m_group.getContext().getState() == ContextState.Live) return;
		if(m_needJumpToPlayTime)
		{
			jumpTo(new Date(m_group.getPlayingTime()));
			m_needJumpToPlayTime = false;
			return;
		}
		NewFrameEvent e = (NewFrameEvent) event;
		m_playingTime = e.getFramePTSMillis();
		m_frameIncomeTime = new Date().getTime();
		if((m_playingTime - m_group.getPlayingTime())*m_group.getMetronome().getDirection() > m_syncPeriod)
		{
			m_player.pause();
			setState(SyncState.Waiting);
		}
		if(m_startCountTime == 0 || m_isJustJumped) {
			m_startCountTime = m_playingTime;
			m_realStartCountTime = new Date().getTime();
			m_empiricSpeed = 0;
			m_isJustJumped = false;
		}
		else {
			m_empiricSpeed = (float)(m_playingTime - m_startCountTime) / (float) (new Date().getTime() - m_realStartCountTime); 
		}
		
		if(m_empiricSpeed > 10)
		{
			m_empiricSpeed = 1;
		}
		
		if(m_lastSyncTime == 0) {
			m_lastSyncTime = new Date().getTime();
		}
		else {
			if(new Date().getTime() - m_lastSyncTime > m_syncPeriod) sync();			
		}
	}
	@Override
	public boolean canDisplayFrame(Date date) {
		// TODO Auto-generated method stub
		return false;
	}
	
	public String getLog()
	{
		String state = "";
		switch (m_syncState) {
		case Playing:
			state = "[Pl]";
			break;
		case Synchronizing:
			state = "[Sn]";
			break;
		case Waiting:
			state = "[Wt]";
		}
		return String.format("(%.3f, %.3f) %tT %s", m_empiricSpeed, m_speed, new Date(m_playingTime), state);
	}
	
	private PropertyChangeSupport m_propertyChangeSupport = new PropertyChangeSupport(this);
	
	  public void addPropertyChangeListener(PropertyChangeListener 
		      listener) {
		  m_propertyChangeSupport.addPropertyChangeListener(listener);
	  }

	  public void removePropertyChangeListener(PropertyChangeListener 
	      listener) {
		  m_propertyChangeSupport.removePropertyChangeListener(listener);
	  }

	  public void addPropertyChangeListener(String propertyName,
	      PropertyChangeListener listener) {
		  m_propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
	  }

	  public void removePropertyChangeListener(String propertyName,
	      PropertyChangeListener listener) {
		  m_propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
	  }


	

}
