/*
 *	RTSystem.java
 *
 *	This file is part of Tritonus: http://www.tritonus.org/
 */

/*
 *  Copyright (c) 2002 by Matthias Pfisterer
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

package org.tritonus.saol.engine;

import java.io.IOException;

import java.lang.reflect.Constructor;

import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import  org.tritonus.share.TDebug;



public class RTSystem
extends Thread
{
	private static final boolean	DEBUG = false;

	private SystemOutput		m_output;
	private Map			m_instrumentMap;
	private boolean			m_bRunning;
	private int			m_nTime;
	private float			m_fTimeStep;
	private int			m_nARate;
	private int			m_nKRate;
	private int			m_nAToKRateFactor;
	private List			m_activeInstruments;
	private List			m_scheduledInstruments;
	private int			m_nScheduledEndTime;
	private float			m_fFloatToIntTimeFactor;
	private float			m_fIntToFloatTimeFactor;



	public RTSystem(SystemOutput output, Map instrumentMap)
	{
		m_output = output;
		m_instrumentMap = instrumentMap;
		// TODO:
		setRates(44100, 100);
		m_activeInstruments = new LinkedList();
		m_scheduledInstruments = new LinkedList();
		m_nScheduledEndTime = Integer.MAX_VALUE;
	}



	private void setRates(int nARate, int nKRate)
	{
		m_nARate = nARate;
		m_nKRate = nKRate;
		m_nAToKRateFactor = nARate / nKRate;
		m_fTimeStep = 1.0F / (float) nKRate;
		// following is only correct for 60 BPM
		m_fFloatToIntTimeFactor = (float) nKRate;
		m_fIntToFloatTimeFactor = m_fTimeStep;
	}



	public void run()
	{
		try
		{
			runImpl();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}



	private void runImpl()
		throws IOException
	{
		m_bRunning = true;
		m_nTime = 0;
		while (m_bRunning)
		{
			doI();
			doK();
			for (int i = 0; i < m_nAToKRateFactor; i++)
			{
				doA();
			}
			advanceTime();
		}
		m_output.close();
	}



	private void doI()
	{
		if (DEBUG)
		{
			TDebug.out("doI()");
			TDebug.out("time: " + getTime());
		}
		synchronized (m_scheduledInstruments)
		{
			Iterator	scheduledInstruments = m_scheduledInstruments.iterator();
			while (scheduledInstruments.hasNext())
			{
				if (DEBUG) { TDebug.out("scheduled instrument"); }
				AbstractInstrument	instrument = (AbstractInstrument) scheduledInstruments.next();
				if (DEBUG) { TDebug.out("instrument start time: " + instrument.getStartTime()); }
				if (getTime() >= instrument.getStartTime())
				{
					if (DEBUG) { TDebug.out("...activating"); }
					scheduledInstruments.remove();
					instrument.doIPass(this);
					m_activeInstruments.add(instrument);
				}
			}
		}
		Iterator	activeInstruments = m_activeInstruments.iterator();
		while (activeInstruments.hasNext())
		{
			AbstractInstrument	instrument = (AbstractInstrument) activeInstruments.next();
			if (getTime() > instrument.getEndTime())
			{
				if (DEBUG) { TDebug.out("...DEactivating"); }
				activeInstruments.remove();
			}
		}
		if (getTime() >= getScheduledEndTime())
		{
			stopEngine();
		}
	}



	private void doK()
	{
		Iterator	activeInstruments = m_activeInstruments.iterator();
		while (activeInstruments.hasNext())
		{
			AbstractInstrument	instrument = (AbstractInstrument) activeInstruments.next();
			instrument.doKPass(this);
		}
	}



	private void doA()
		throws IOException
	{
		// TDebug.out("doA()");
		m_output.clear();
		Iterator	activeInstruments = m_activeInstruments.iterator();
		while (activeInstruments.hasNext())
		{
			// TDebug.out("doA(): has active Instrument");
			AbstractInstrument	instrument = (AbstractInstrument) activeInstruments.next();
			instrument.doAPass(this);
		}
		m_output.emit();
	}



	public void scheduleInstrument(String strInstrumentName, float fStartTime, float fDuration)
	{
		AbstractInstrument	instrument = createInstrumentInstance(strInstrumentName);
		int			nStartTime = Math.round(fStartTime * m_fFloatToIntTimeFactor);
		int			nEndTime = Math.round((fStartTime + fDuration) * m_fFloatToIntTimeFactor);
		instrument.setStartAndEndTime(nStartTime, nEndTime);
		synchronized (m_scheduledInstruments)
		{
			m_scheduledInstruments.add(instrument);
			if (DEBUG)
			{
				TDebug.out("adding instrument");
				TDebug.out("start: " + nStartTime);
				TDebug.out("end: " + nEndTime);
			}
		}
	}



	public void scheduleEnd(float fEndTime)
	{
		m_nScheduledEndTime = Math.round(fEndTime * m_fFloatToIntTimeFactor);
		// TODO:
	}



	public void stopEngine()
	{
		m_bRunning = false;
	}



	private void advanceTime()
	{
		m_nTime++;
	}


	public int getTime()
	{
		return m_nTime;
	}

	public void output(float fValue)
	{
		m_output.output(fValue);
	}



	private int getScheduledEndTime()
	{
		return m_nScheduledEndTime;
	}



	private AbstractInstrument createInstrumentInstance(String strInstrumentName)
	{
		AbstractInstrument	instrument = null;
		Class	instrumentClass = (Class) m_instrumentMap.get(strInstrumentName);
		try
		{
			Constructor	constructor = instrumentClass.getConstructor(new Class[]{RTSystem.class});
			instrument = (AbstractInstrument) constructor.newInstance(new Object[]{this});
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		return instrument;
	}
}



/*** RTSystem.java ***/
