package com.videonext.mplayer;

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


import com.videonext.mplayer.SinglePlayerSyncronizer.SyncState;
import com.videonext.mplayer.api.ControllerEvent;
import com.videonext.mplayer.api.IControllerListener;
import com.videonext.mplayer.api.IPlayer;
import com.videonext.mplayer.api.IPlayerController;
import com.videonext.mplayer.api.IPlayerGroup;
import com.videonext.mplayer.api.IPlayerURLProvider;
import com.videonext.mplayer.api.events.ChangeStateEvent;
import com.videonext.mplayer.api.events.NewFrameEvent;

public class PlayerGroup implements IControllerListener, IPlayerGroup {

	
	private ArrayList<IControllerListener> m_listeners = new ArrayList<IControllerListener>();
	//private ArrayList<IPlayer> m_players = new ArrayList<IPlayer>();
	private ArrayList<SinglePlayerSyncronizer> m_syncronizers = new ArrayList<SinglePlayerSyncronizer>();

	public ArrayList<SinglePlayerSyncronizer> getSynchronizers() {return m_syncronizers; }
	private Metronome m_metronome;
	public Metronome getMetronome() { return m_metronome; }
	private Timer m_metronomeTimer;
	private IPlayerURLProvider m_provider;
	private long m_playingTime = new Date().getTime(); 
	public long getPlayingTime() { return m_playingTime; }
	
	
	
	private final int m_metronomePeriod = 200;
	private final int m_speedUpdatePeriod = 3000;
	enum MetronomeState {
		Play,
		Pause
	}
	
	public class Metronome extends TimerTask {
		private MetronomeState m_state = MetronomeState.Pause;
		private float m_speed = 1.f;
		private long m_realTime = new Date().getTime();
		private float m_magicRealtimeCoeff = 1.f;
		private void setMagicRealTimeCoeff(float val) { m_magicRealtimeCoeff = Math.min(1.1f, Math.max(0.9f, val)); }
		public float getMagicCoeff() {return m_magicRealtimeCoeff; }
		private int m_direction = PlayerGroup.Forward_Direction;
		private long m_prevSpeedUpdate = 0;
		
		public void setDirection(int direction) {m_direction = direction;}
		public int getDirection() { return m_direction; }
		public void play() { m_state = MetronomeState.Play; }
		public void pause() { m_state = MetronomeState.Pause; }
		public MetronomeState getState() {return m_state;} 
		
		public void setSpeed(float value) {m_speed = value;}
		public float getSpeed() { return Math.min(m_speed, m_maxSpeed); }
		
		private float m_maxSpeed = 24;
		public float getMaxSpeed() { return m_maxSpeed; }
		public void setMaxSpeed(float value) { 
			if(m_speed > value) 
			{
				for (SinglePlayerSyncronizer p : m_syncronizers) {				
					p.setSpeed(value);
					p.switchToPlaying();
				}
			}
			m_maxSpeed = value;
		}
		
		@Override
		public void run() {
			long realTime = new Date().getTime();
			long timeDiff = realTime - m_realTime;
			m_realTime = realTime;
			if(m_state == MetronomeState.Pause || m_context.getState() == ContextState.Live) return;				
			m_playingTime += (long)(timeDiff * getSpeed() * m_magicRealtimeCoeff  * m_direction);		
			controllerUpdate((ControllerEvent)new NewFrameEvent(PlayerGroup.this, m_playingTime, m_playingTime, m_playingTime));
			if(m_prevSpeedUpdate == 0) 
				m_prevSpeedUpdate = realTime;
			if(realTime  - m_prevSpeedUpdate> m_speedUpdatePeriod)
			{
				int playerNum = 0; 
				float magicRealTimeCoeff = 0;
				for(SinglePlayerSyncronizer s : m_syncronizers){
					if(s.getState() == SyncState.Waiting) continue;
					if(s.getSpeed()!=0 && s.getEmpiricSpeed()!=0)
					{
						magicRealTimeCoeff += s.getEmpiricSpeed() / s.getSpeed();
						++playerNum;
					}
				}
				if(playerNum != 0)
				{
					magicRealTimeCoeff /= playerNum;				
					setMagicRealTimeCoeff(magicRealTimeCoeff);
				}
				m_prevSpeedUpdate = realTime;
			}
			changeSupport.firePropertyChange("log", "", getLog());
		}
	};
	
	private IPlayer[] getPlayers()
	{
		IPlayer[] res = new IPlayer[m_syncronizers.size()];
		int i=0;
		for(SinglePlayerSyncronizer s : m_syncronizers)
		{
			res[i] = s.getPlayer();
			++i;
		}
		return res;
	}
	
	public PlayerGroup()
	{
		setMetronome();
		NetObserver.get().registerPlayerGroup(this);
	}
	
	
	void setMetronome()
	{
		m_metronomeTimer = new Timer();
		m_metronome = new Metronome();
		m_metronomeTimer.schedule(m_metronome, m_metronomePeriod, m_metronomePeriod);
	}


	@Override
	public void play(int direction) {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers) {
				p.getPlayer().play(direction);
			}
			m_metronome.setDirection(direction);
			m_metronome.play();
		}
	}

	@Override
	public void pause() {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers) {
				p.getPlayer().pause();
			}
			m_metronome.pause();
		}		
	}

	@Override
	public void stop() {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers) {
				p.getPlayer().stop();
			}
			m_metronome.pause();
		}
	}
	
	@Override
	public void close() {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers) {
				p.getPlayer().close();
			}
			m_metronome.pause();
		}
		
	}

	@Override
	public int getState() {
		switch (m_metronome.getState())
		{
		case Play:
			return Playing_Server;
		case Pause:
			return Idle;
		}
		return Idle;
	}

	@Override
	public void addControllerListener(IControllerListener listener) {
		synchronized (m_listeners) {
			m_listeners.add(listener);
		}
		
	}

	@Override
	public void removeControllerListener(IControllerListener listener) {
		synchronized (m_listeners) {
			m_listeners.remove(listener);
		}
		
	}

	@Override
	public void setAspectRation(int value) {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers) {
				p.getPlayer().setAspectRation(value);
			}
		}
	}
	
	 

	@Override
	public void changeSpeed(float value) {
		m_metronome.setSpeed(value);
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers) {				
				p.setSpeed(value);
				p.switchToPlaying();
			}
		}		
	}

	@Override
	public void jumpTo(Date date) {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers) {
				p.jumpTo(date);
			}
		}		
	}

	@Override
	public void setStepMode(boolean flag) {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers) {
				p.getPlayer().setStepMode(flag);
			}
			//TODO: Metronome
		}
		
	}

	@Override
	public void nextStep(int direction) {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers)
			{
				p.getPlayer().nextStep(direction);
			}
			//TODO: Metronome
		}
		
	}

	@Override
	public void setBrightness(float value) {
		synchronized (m_syncronizers) {
			for (SinglePlayerSyncronizer p : m_syncronizers)
			{
				p.getPlayer().setBrightness(value);
			}
		}
		
	}

	@Override
	public void setPlayerURLProvider(IPlayerURLProvider provider) {
		m_provider = provider;		
	}
	
	enum ContextState {
		Live,
		Archive
	}
	class Context {
		private ContextState m_state = ContextState.Live;
		private Date m_startTime = new Date();
		private Date m_endTime = new Date();
		public void setState(ContextState state) { m_state = state; }
		public ContextState getState() { return m_state; }
		public void setTime(Date startTime, Date endTime) { m_startTime = startTime; m_endTime = endTime; }
		public Date getStartTime() { return m_startTime; }
		public Date getEndTime() { return m_endTime; }
		public void Apply(IPlayer player) {
			synchronized (player) {
				String[] url = null;
				switch (m_state)
				{
				case Live:
					url = m_provider.getLiveURL(new IPlayer[] {player});
					break;
				case Archive:
					url = m_provider.getArchiveURL(new IPlayer[] {player}, m_startTime, m_endTime);
					break;
				}
				if(url != null && url.length > 0)
					Apply(player, url[0]);					
			}
		}
		public void Apply(IPlayer player, String url)
		{			
			player.close();
			player.open(url);
		}
	}
	
	private Context m_context = new Context(); 
	public Context getContext() { return m_context; }

	@Override
	public void switchToLive() {
		m_context.setState(ContextState.Live);
		synchronized (m_syncronizers) {
			String[] urls = m_provider.getLiveURL(getPlayers());
			int i=0;
			for (SinglePlayerSyncronizer p : m_syncronizers)
			{
				m_context.Apply(p.getPlayer(),urls[i]);
				++i;
			}
		}
	}

	@Override
	public void switchToArchive(Date startTime, Date endTime) {
		m_context.setState(ContextState.Archive);
		m_context.setTime(startTime, endTime);
		
		m_playingTime = startTime.getTime();
		synchronized (m_syncronizers) {
			String[] urls = m_provider.getArchiveURL
					(getPlayers(),startTime, endTime);
			int i=0;
			for (SinglePlayerSyncronizer p : m_syncronizers)
			{
				m_context.Apply(p.getPlayer(),urls[i]);
				++i;
			}
		}		
	}

	@Override
	public void addPlayer(IPlayer newPlayer) {
		synchronized (m_syncronizers) {
			m_context.Apply(newPlayer);
			//m_players.add(newPlayer);			
			SinglePlayerSyncronizer s = new SinglePlayerSyncronizer(newPlayer, this);		
			m_syncronizers.add(s);
			newPlayer.addControllerListener(this);
			NetObserver.get().registerPlayer(s);
		}
		
	}

	@Override
	public void removePlayer(IPlayer oldPlayer) {
		synchronized (m_syncronizers) {
			SinglePlayerSyncronizer sync = null;
			for(SinglePlayerSyncronizer s : m_syncronizers)
			{
				if(s.getPlayer() == oldPlayer) {sync = s; break;}				
			}
			if(sync != null) 
			{
				sync.Dispose();
				m_syncronizers.remove(sync);
			}
			//m_players.remove(oldPlayer);						
		}
		
	}
	
	private void pushToListeners(ControllerEvent event)
	{
		for(IControllerListener l : m_listeners)
		{
			l.controllerUpdate(event);
		}
	}
	
	class JumperTask implements IControllerListener {
		IPlayerController m_pc;
		
		public JumperTask(IPlayerController pc)
		{
			m_pc = pc;
		}

		@Override
		public void controllerUpdate(ControllerEvent event) {
			System.out.println("motherfucker yohoho");
			m_pc.removeControllerListener(this);
			if(getContext().getState() == ContextState.Live) return ;
			m_pc.jumpTo(new Date(m_playingTime));			
		}

		@Override
		public boolean canDisplayFrame(Date date) {
			System.out.println("motherfucker yohoho");
			m_pc.removeControllerListener(this);
			if(getContext().getState() == ContextState.Live) return true;
			m_pc.jumpTo(new Date(m_playingTime));			
			return false;
		}
	}
	
	private SinglePlayerSyncronizer getSynchronizer(IPlayer player)
	{
		for(SinglePlayerSyncronizer s : m_syncronizers)
		{
			if(s.getPlayer() == player)
			{
				return s;
			}
		}
		return null;
	}

	@Override
	public void controllerUpdate(ControllerEvent event) {
		if(event instanceof ChangeStateEvent)
		{
			ChangeStateEvent e = (ChangeStateEvent) event;
			switch(e.getCurrentState())
			{
			case Opened:
				IPlayerController pc = (IPlayerController)e.getSource();
				if(m_metronome.getState() == MetronomeState.Play)
				{					
					pc.play(m_metronome.getDirection());
					SinglePlayerSyncronizer s = getSynchronizer((IPlayer) pc);
					s.setJumpToPlayTime();
				}
				break;
			default:
				break;
			}
			if(e.getSource() == m_syncronizers.get(0).getPlayer())
			{
				pushToListeners(event);
			}
		}
		else if(event instanceof NewFrameEvent)
		{
			NewFrameEvent e = (NewFrameEvent) event;
			switch(m_context.getState())
			{
			case Live:
				if(e.getFramePTSMillis() > m_playingTime)
				{
					m_playingTime = e.getFramePTSMillis();
					pushToListeners(event);
				}
				break;
			case Archive:
				if(e.getSource() == this)
				{
					pushToListeners(event);
				}
				break;
			}
		}
	}
	
	public String getLog()
	{
		String res = "";
		synchronized (m_syncronizers) {
			for(SinglePlayerSyncronizer s : m_syncronizers)
			{
				res+=String.format("%s\n", s.getLog()); 
			}
		}
		res += String.format("(%.3f, %.3f) %tT [Mt]\n%s", m_metronome.getMagicCoeff(), m_metronome.getSpeed(), new Date(m_playingTime),
				NetObserver.get().getLog());
		return res;
	}

	@Override
	public boolean canDisplayFrame(Date date) {
		// TODO Auto-generated method stub
		return true;
	}
	
	private PropertyChangeSupport changeSupport = 
		      new PropertyChangeSupport(this);

		  public void addPropertyChangeListener(PropertyChangeListener 
		      listener) {
		    changeSupport.addPropertyChangeListener(listener);
		  }

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

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

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

		@Override
		public void setMute(boolean val) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void changeJitterBufferLen(int value) {
			// TODO Auto-generated method stub
			
		}

}
