package com.videonext.mplayer;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Timer;
import java.util.TimerTask;

import com.videonext.mplayer.SinglePlayerSyncronizer.SyncState;

public class NetObserver {
	public enum States {
		NormalBandwith,
		LowBandwithStart,
		SearchingTopSpeed,
		LowBandwithPlay
	}
	
	private final long ObserverPeriod = 5000;
		
	private Timer m_observeTimer = new Timer();
	private States m_state = States.NormalBandwith;
	private ArrayList<PlayerGroup> m_playerGroups = new ArrayList<PlayerGroup>();
	private HashMap<Object, Long> m_synchronizingPlayerTime = new HashMap<Object, Long>();
	private float m_minPlayerSpeed = 0;
	private void setMinPlayerSpeed(float val)
	{ m_minPlayerSpeed = Math.max(0.1f, val); }
	private float m_currentPlayerSpeed = 0;
	private int m_lowBandwithStartPeriods = 0;
	private int m_searchingTopSpeedPeriods = 0;
	class SyncStateListener implements PropertyChangeListener {
		
		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			if(!(evt.getNewValue() instanceof SinglePlayerSyncronizer.SyncState)) return;
			SinglePlayerSyncronizer.SyncState newState = (SyncState) evt.getNewValue();
			synchronized (m_synchronizingPlayerTime) {
				if(newState == SyncState.Synchronizing) {
						m_synchronizingPlayerTime.put(evt.getSource(), new Date().getTime());
					}
					else {
						m_synchronizingPlayerTime.remove(evt.getSource());
					}
			}
		}
	}
	
	private SyncStateListener m_syncStateListener = new SyncStateListener();
	
	public void registerPlayer(SinglePlayerSyncronizer s)
	{
		s.addPropertyChangeListener("SyncState", m_syncStateListener);
	}
	
	private void updateMaxSpeed(float value)
	{
		for(PlayerGroup p : m_playerGroups)
			p.getMetronome().setMaxSpeed(value);
	}
	
	private void normalBandwith()
	{
		float latePlayerAvgSpeed = 0;
		synchronized (m_synchronizingPlayerTime) {
			Boolean isPlayersLate = false;
			for(Entry<Object, Long> latePalyer : m_synchronizingPlayerTime.entrySet())
			{
				if((new Date().getTime() - latePalyer.getValue()) > ObserverPeriod) isPlayersLate = true;
				latePlayerAvgSpeed += ((SinglePlayerSyncronizer)latePalyer.getKey()).getEmpiricSpeed();
			}
			if(m_synchronizingPlayerTime.size() > 0)
			{
				if(isPlayersLate)
				{
					latePlayerAvgSpeed /= m_synchronizingPlayerTime.size();
					setMinPlayerSpeed(latePlayerAvgSpeed * 0.75f);
					updateMaxSpeed(m_minPlayerSpeed);
					m_state = States.LowBandwithStart;
				}
					
			}		
		}
		
	}
	
	private void lowBandwithStart()
	{
		synchronized (m_playerGroups) {
			for(PlayerGroup p : m_playerGroups)
			{
				ArrayList<SinglePlayerSyncronizer> synchronizers = p.getSynchronizers();
				synchronized (synchronizers) {
					for(SinglePlayerSyncronizer s : synchronizers)
					{
						if(s.getState() != SinglePlayerSyncronizer.SyncState.Synchronizing) continue;
						if(m_searchingTopSpeedPeriods > 3)
						{
							m_minPlayerSpeed *= 0.9f;
							updateMaxSpeed(m_minPlayerSpeed);
							m_lowBandwithStartPeriods = 0;
							return;
						}
						m_searchingTopSpeedPeriods++;
						return;
					}					
				}
			}
		}
		m_lowBandwithStartPeriods = 0;
		m_currentPlayerSpeed = m_minPlayerSpeed;
		m_state = States.SearchingTopSpeed; 
	}
	
	private void searchingTopSpeed()
	{
		synchronized (m_playerGroups) {
			for(PlayerGroup p : m_playerGroups)
			{
				ArrayList<SinglePlayerSyncronizer> synchronizers = p.getSynchronizers();
				synchronized (synchronizers) {
					for(SinglePlayerSyncronizer s : synchronizers)
					{
						if(s.getState() != SinglePlayerSyncronizer.SyncState.Synchronizing) continue;
						if(m_lowBandwithStartPeriods > 1)
						{ 
							m_currentPlayerSpeed = (m_currentPlayerSpeed + m_minPlayerSpeed) / 2;
							updateMaxSpeed(m_currentPlayerSpeed);
							m_searchingTopSpeedPeriods = 0;
							m_state = States.LowBandwithPlay;
							return;
						}
						m_lowBandwithStartPeriods++;
						return;
					}					
				}
			}
		}
		m_currentPlayerSpeed+=0.05;
		updateMaxSpeed(m_currentPlayerSpeed);
	}
	
	private void lowBandwithPlay()
	{
		int latePlayerCount = 0;
		float latePlayerAvgSpeed = 0;
		synchronized (m_playerGroups) {
		for(PlayerGroup p : m_playerGroups)
		{
			ArrayList<SinglePlayerSyncronizer> synchronizers = p.getSynchronizers();
			synchronized (synchronizers) {
			
			for(SinglePlayerSyncronizer s : synchronizers)
			{
				if(s.getEmpiricSpeed() / s.getSpeed() > 0.5 || s.getState() != SinglePlayerSyncronizer.SyncState.Synchronizing) continue;
				++latePlayerCount;
				latePlayerAvgSpeed+=s.getEmpiricSpeed();
			}
			}
		}
		if(latePlayerCount > 0)
		{
			latePlayerAvgSpeed /= latePlayerCount;
			setMinPlayerSpeed(latePlayerAvgSpeed * 0.75f);
			updateMaxSpeed(m_minPlayerSpeed);
			m_state = States.LowBandwithStart;
		}
		}
	}
	
	private class ObserveTask extends TimerTask {

		@Override
		public void run() {
			switch (m_state)
			{
			case LowBandwithPlay:
				lowBandwithPlay();
				break;
			case LowBandwithStart:
				lowBandwithStart();
				break;
			case NormalBandwith:
				normalBandwith();
				break;
			case SearchingTopSpeed:
				searchingTopSpeed();
				break;
			}
			
		}
		
	}
	
	private static NetObserver m_netObserver = new NetObserver();
	
	protected NetObserver()
	{
		m_observeTimer.schedule(new ObserveTask(), ObserverPeriod, ObserverPeriod);
	}
	
	public static NetObserver get()
	{
		return m_netObserver;
	}
	
	public void registerPlayerGroup(PlayerGroup group)
	{
		synchronized (m_playerGroups) {
			m_playerGroups.add(group);
			for(SinglePlayerSyncronizer s : group.getSynchronizers())
			{
				s.addPropertyChangeListener("SyncState", m_syncStateListener);
			}
		}		
	}
	
	public void removePlayerGroup(PlayerGroup group)
	{
		synchronized (m_playerGroups) {
			m_playerGroups.remove(group);
		}
	}
	
	public String getLog()
	{
		String state = "";
		switch(m_state)
		{
		case LowBandwithPlay:
			state = "[LBP]";
			break;
		case LowBandwithStart:
			state = "[LBS]";
			break;
		case NormalBandwith:
			state ="[NB]";
			break;
		case SearchingTopSpeed:
			state ="[STS]";
			break;
		}
		return String.format("%.3f. %.3f, %s", m_minPlayerSpeed, m_currentPlayerSpeed, state);
	}
	
	

}
