package kr.util.audio;

import java.util.ArrayList;
import java.util.List;


public class MixerAudioClip extends AudioClip
{
	public static final int OUTPUT_LINE_NUMBER = 0;
	private static class LineData
	{
		AudioClip ac;
		
		//mutes the audio track
		boolean muted;
		
		//serves the same purpose as mute, but can be used to signify that the track
		// is not currently playing, even if unmuted by the user
		boolean on;
		float volume;
		float realVolume; // volume adjusted for muted and less then 100 % channels
		String name;

		boolean hidden;
		
		public LineData(AudioClip ac, boolean muted, boolean hidden, float volume, String name)
		{
			this.ac = ac;
			this.muted = muted;
			this.volume = volume;
			this.name = name;
			this.on = true;
			this.hidden = hidden;
		}

		public boolean isReallyMuted() {
			return muted || !on || ac == null;
		}
	}
	
	private List<LineData> lines = new ArrayList<LineData>();
	private int numChannels;
	private int maxLength;
	
	public MixerAudioClip(int numChannels) {
		this.numChannels = numChannels;
		lines.add(new LineData(this, false, false, 1, "Output")); // we are line zero
		calcRealVolumes();
	}

	private void calcRealVolumes() {
		float totalVol = 0;
		
		for(int i = 1; i < lines.size(); i++)
		{
			LineData ld = lines.get(i);
			totalVol += ld.isReallyMuted() ? 0 : ld.volume;
		}

		LineData out = lines.get(OUTPUT_LINE_NUMBER);
		out.realVolume = out.isReallyMuted() ? 0 : out.volume;
			
		for(int i = 1; i < lines.size(); i++)
		{
			LineData ld = lines.get(i);
			if(totalVol == 0) ld.realVolume = 0;
			else
				ld.realVolume = (ld.muted ? 0 : ld.volume) / totalVol * out.realVolume;
		}
	}

	/**
	 * Returns line number
	 */
	public int addLine(AudioClip ac, String name, boolean hidden)
	{
		lines.add(new LineData(ac,false,hidden, 1, name));
		updateMaxLength(lines.get(lines.size() -1));

		return lines.size()-1;
	}
	
	private void updateMaxLength(LineData ld)
	{
		if(ld.ac != null && ld.ac.length() > maxLength)
			maxLength = ld.ac.length();
	}
	

	@Override
	public int getNumChannels() {
		return numChannels;
	}

	@Override
	public int length() {
		return maxLength;
	}

	@Override
	public int getRate() {
		for(int i = 1; i < lines.size(); i++)
		{
			LineData sd = lines.get(i);
			
			if(sd.ac != null && sd.ac.getRate() != 0) return sd.ac.getRate();
		}	
		
		return 0;
	}
	

	@Override
	public synchronized byte byteAt(int index) {
		float total = 0;

		for(int i = 1; i < lines.size(); i++)
		{
			LineData sd = lines.get(i);
			
			if(sd.isReallyMuted()) continue;
			int audioChannel = Math.min(sd.ac.getNumChannels()-1, index % numChannels);
			int myIndex = index / numChannels * sd.ac.getNumChannels();
			//System.out.println("my index is "+myIndex+" audio channel is "+audioChannel);
				
			if(sd.ac.length() > myIndex)
			{
				total += sd.ac.byteAt(myIndex+audioChannel) * sd.realVolume;
			}
		}
		
		return (byte)(total);
	}
	
	private float [] mixerBuffer;

	@Override
	public synchronized void copyTo(int offset, int length, byte[] dest, int destOffset) {
//		//TODO: currently only handles 8 bit sound
//		
//		if(mixerBuffer == null || mixerBuffer.length < length)
//			mixerBuffer = new float[length];
//		
//		boolean firstTime = true;
//		
//		for(int i = 1; i < lines.size(); i++)
//		{
//			LineData sd = lines.get(i);
//			
//			if(sd.isReallyMuted()) continue; 
//			
//			int lineChannels = sd.ac.getNumChannels();
//			
//			sd.ac.copyTo(offset, length * lineChannels / numChannels, dest, destOffset);
//			
//			if(firstTime)
//			{
//				firstTime = false;
//				for(int j = 0; j < length; j++)
//				{
//					mixerBuffer[j] = dest[destOffset + j * lineChannels / numChannels  ] * sd.realVolume;
//				}
//			}
//			else
//			{
//				for(int j = 0; j < length; j++)
//				{
//					mixerBuffer[j] += dest[destOffset + j * lineChannels / numChannels] * sd.realVolume;
//				}
//			}				
//		}
//		
//		for(int j = 0; j < mixerBuffer.length; j++)
//		{
//			dest[destOffset+j] = (byte) mixerBuffer[j];
//		}
		
		//PERF: very sloooooooooowwwwwwwwww
		for(int i = 0; i < length; i++)
		{
			dest[i+destOffset] = byteAt(i+offset); 
		}
	}

	public void updateMuteFlag(boolean muteFlag, int line)
	{
		lines.get(line).muted = muteFlag;
		calcRealVolumes();
	}

	public void updateOnFlag(boolean onFlag, int line)
	{
		lines.get(line).on = onFlag;
		calcRealVolumes();
	}

	public void updateVolume(float vol, int line)
	{
		lines.get(line).volume = vol;
		calcRealVolumes();
	}

	public int getNumLines() {
		return lines.size();
	}

	public String getName(int lineNumber) {
		return lines.get(lineNumber).name;
	}

	public boolean isMutedFlag(int lineNumber) {
		return lines.get(lineNumber).muted;
	}

	public boolean isOnFlag(int lineNumber) {
		return lines.get(lineNumber).on;
	}

	public float getVolume(int lineNumber) {
		return lines.get(lineNumber).volume;
	}

	public void updateMuteFlag(boolean muteFlag, AudioClip clip) {
		for(LineData ld : lines)
		{
			if(ld.ac == clip)
			{
				ld.muted = muteFlag;
				break;
			}
		}
	}

	public void replaceClip(int lineNumber, AudioClip newClip) {
		lines.get(lineNumber).ac = newClip;
		
		updateMaxLength(lines.get(lineNumber));
	}

	/**
	 * If hidden, the line should not be displayed in the control
	 * panel visible to the user
	 */
	public boolean isLineHidden(int i) {
		return lines.get(i).hidden;
	}

}
