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

/*
 *  Copyright (c) 1999 - 2004 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.sampled.mixer.alsa;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Iterator;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.Line;
import javax.sound.sampled.Mixer;

import org.tritonus.share.TDebug;
import org.tritonus.share.TSettings;
import org.tritonus.share.sampled.mixer.TMixer;
import org.tritonus.share.sampled.mixer.TMixerInfo;
import org.tritonus.share.sampled.mixer.TSoftClip;
import org.tritonus.share.GlobalInfo;

import org.tritonus.lowlevel.alsa.Alsa;
import org.tritonus.lowlevel.alsa.AlsaPcm;
import org.tritonus.lowlevel.alsa.AlsaPcmHWParams;
import org.tritonus.lowlevel.alsa.AlsaPcmHWParamsFormatMask;



public class AlsaDataLineMixer
extends TMixer
{
	private static final AudioFormat[]	EMPTY_AUDIOFORMAT_ARRAY = new AudioFormat[0];
	private static final int	CHANNELS_LIMIT = 32;

	// default buffer size in bytes.
	private static final int	DEFAULT_BUFFER_SIZE = 32768;

	/*	The name of the sound card this mixer is representing.
	 */
	private String	m_strPcmName;



	public static String getDeviceNamePrefix()
	{
		if (TSettings.AlsaUsePlughw)
		{
			return "plughw";
		}
		else
		{
			return "hw";
		}
	}


	public static String getPcmName(int nCard)
	{
		String	strPcmName = getDeviceNamePrefix()
			+ ":" + nCard;
		if (TSettings.AlsaUsePlughw)
		{
			// strPcmName += ",0";
		}
		return strPcmName;
	}



	public AlsaDataLineMixer()
	{
		this(0);
	}



	public AlsaDataLineMixer(int nCard)
	{
		this(getPcmName(nCard));
	}



	public AlsaDataLineMixer(String strPcmName)
	{
		super(new TMixerInfo(
			"Alsa DataLine Mixer (" + strPcmName + ")",
			GlobalInfo.getVendor(),
			"Mixer for the Advanced Linux Sound Architecture (card " + strPcmName + ")",
			GlobalInfo.getVersion()),
		      new Line.Info(Mixer.class));
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.<init>(String): begin."); }
		m_strPcmName = strPcmName;
		List<AudioFormat>	sourceFormats = getSupportedFormats(AlsaPcm.SND_PCM_STREAM_PLAYBACK);
		List<AudioFormat>	targetFormats = getSupportedFormats(AlsaPcm.SND_PCM_STREAM_CAPTURE);
		List<Line.Info>	sourceLineInfos = new ArrayList<Line.Info>();
		Line.Info	sourceLineInfo = new DataLine.Info(
			SourceDataLine.class,
			sourceFormats.toArray(EMPTY_AUDIOFORMAT_ARRAY),
			AudioSystem.NOT_SPECIFIED,
			AudioSystem.NOT_SPECIFIED);
		sourceLineInfos.add(sourceLineInfo);
		List<Line.Info>	targetLineInfos = new ArrayList<Line.Info>();
		Line.Info	targetLineInfo = new DataLine.Info(
			TargetDataLine.class,
			(AudioFormat[]) targetFormats.toArray(EMPTY_AUDIOFORMAT_ARRAY),
			AudioSystem.NOT_SPECIFIED,
			AudioSystem.NOT_SPECIFIED);
		targetLineInfos.add(targetLineInfo);
		setSupportInformation(sourceFormats,
				      targetFormats,
				      sourceLineInfos,
				      targetLineInfos);
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.<init>(String): end."); }
	}



	public String getPcmName()
	{
		return m_strPcmName;
	}



	//////////////// Line //////////////////////////////////////


	// TODO: allow real close and reopen of mixer
	public void open()
	{
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.open(): begin"); }

		// currently does nothing

		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.open(): end"); }
	}



	public void close()
	{
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.close(): begin"); }

		// currently does nothing

		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.close(): end"); }
	}





	//////////////// Mixer //////////////////////////////////////


	public int getMaxLines(Line.Info info)
	{
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getMaxLines(): begin"); }
		// TODO:
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getMaxLines(): end"); }
		return 0;
	}




	//////////////// private //////////////////////////////////////


	// nBufferSize is in bytes!
	protected SourceDataLine getSourceDataLine(AudioFormat format, int nBufferSize)
		throws LineUnavailableException
	{
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSourceDataLine(): begin"); }
		if (TDebug.TraceMixer)
		{
			TDebug.out("AlsaDataLineMixer.getSourceDataLine(): format: " + format);
			TDebug.out("AlsaDataLineMixer.getSourceDataLine(): buffer size: " + nBufferSize);
		}
		if (nBufferSize < 1)
		{
			nBufferSize = DEFAULT_BUFFER_SIZE;
		}
		AlsaSourceDataLine	sourceDataLine = new AlsaSourceDataLine(this, format, nBufferSize);
		// sourceDataLine.start();
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSourceDataLine(): returning: " + sourceDataLine); }
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSourceDataLine(): end"); }
		return sourceDataLine;
	}


	// nBufferSize is in bytes!
	protected TargetDataLine getTargetDataLine(AudioFormat format, int nBufferSize)
		throws LineUnavailableException
	{
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getTargetDataLine(): begin"); }
		int			nBufferSizeInBytes = nBufferSize * format.getFrameSize();
		AlsaTargetDataLine	targetDataLine = new AlsaTargetDataLine(this, format, nBufferSizeInBytes);
		// targetDataLine.start();
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getTargetDataLine(): returning: " + targetDataLine); }
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getTargetDataLine(): end"); }
		return targetDataLine;
	}



	protected Clip getClip(AudioFormat format)
		throws LineUnavailableException
	{
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getClip(): begin"); }
		Clip	clip = new TSoftClip(this, format);
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getClip(): end"); }
		return clip;
	}



	/*
	  nDirection: should be AlsaPcm.SND_PCM_STREAM_PLAYBACK or
	  AlsaPcm.SND_PCM_STREAM_CAPTURE.
	 */
	private List<AudioFormat> getSupportedFormats(int nDirection)
	{
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): begin"); }
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): direction: " + nDirection); }
		List<AudioFormat>	supportedFormats = new ArrayList<AudioFormat>();
		AlsaPcm	alsaPcm = null;
		try
		{
			alsaPcm = new AlsaPcm(
				getPcmName(),
				nDirection,
				0);	// no special mode
		}
		catch (Exception e)
		{
			if (TDebug.TraceAllExceptions) { TDebug.out(e); }
			throw new RuntimeException("cannot open pcm");
		}
		int	nReturn;
		AlsaPcmHWParams	hwParams = new AlsaPcmHWParams();
		nReturn = alsaPcm.getAnyHWParams(hwParams);
		if (nReturn != 0)
		{
			TDebug.out("AlsaDataLineMixer.getSupportedFormats(): getAnyHWParams(): " + Alsa.getStringError(nReturn));
			throw new RuntimeException(Alsa.getStringError(nReturn));
		}
		AlsaPcmHWParamsFormatMask	formatMask = new AlsaPcmHWParamsFormatMask();
		int	nMinChannels = hwParams.getChannelsMin();
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): min channels: " + nMinChannels); }
		int	nMaxChannels = hwParams.getChannelsMax();
		nMaxChannels = Math.min(nMaxChannels, CHANNELS_LIMIT);
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): max channels: " + nMaxChannels); }
		hwParams.getFormatMask(formatMask);
		for (int i = 0; i < 32; i++)
		{
			if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): checking ALSA format index: " + i); }
			if (formatMask.test(i))
			{
				if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): ...supported"); }
				AudioFormat	audioFormat = AlsaUtils.getAlsaFormat(i);
				if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): adding AudioFormat: " + audioFormat); }
				addChanneledAudioFormats(supportedFormats, audioFormat, nMinChannels, nMaxChannels);
				// supportedFormats.add(audioFormat);
			}
			else
			{
			if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): ...not supported"); }
			}
		}
		// TODO: close/free mask & hwParams?
		alsaPcm.close();
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.getSupportedFormats(): end"); }
		return supportedFormats;
	}



	private static void addChanneledAudioFormats(
		Collection<AudioFormat> collection,
		AudioFormat protoAudioFormat,
		int nMinChannels,
		int nMaxChannels)
	{
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.addChanneledAudioFormats(): begin"); }
		for (int nChannels = nMinChannels; nChannels <= nMaxChannels; nChannels++)
		{
			AudioFormat	channeledAudioFormat = getChanneledAudioFormat(protoAudioFormat, nChannels);
			if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.addChanneledAudioFormats(): adding AudioFormat: " + channeledAudioFormat); }
			collection.add(channeledAudioFormat);
		}
		if (TDebug.TraceMixer) { TDebug.out("AlsaDataLineMixer.addChanneledAudioFormats(): end"); }
	}



	// TODO: better name
	// TODO: calculation of frame size is not perfect
	private static AudioFormat getChanneledAudioFormat(AudioFormat audioFormat, int nChannels)
	{
		AudioFormat	channeledAudioFormat = new AudioFormat(
			audioFormat.getEncoding(),
			audioFormat.getSampleRate(),
			audioFormat.getSampleSizeInBits(),
			nChannels,
			(audioFormat.getSampleSizeInBits() / 8) * nChannels,
			audioFormat.getFrameRate(),
			audioFormat.isBigEndian());
		return channeledAudioFormat;
	}
}



/*** AlsaDataLineMixer.java ***/
