/*
 *  Copyright (c) 2007 - 2008 by Damien Di Fede <ddf@compartmental.net>
 *
 *   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.
 */

package ddf.minim;

import java.util.HashSet;
import java.util.Vector;

/**
 * An <code>SignalChain</code> is a list of {@link AudioSignal AudioSignals}
 * that gives you the ability to enable (unmute) and disable (mute) signals.
 * When you add a signal, it is added to the end of the chain and is enabled.
 * When you remove a signal, signals further down the chain are moved up a slot.
 * <code>SignalChain</code> is itself an <code>AudioSignal</code>, so you
 * can easily create groups of signals that can be enabled/disabled together by
 * putting them in an <code>SignalChain</code> and then adding that chain to a
 * <code>Polyphonic</code> object as a single signal. When the signal chain is
 * asked to generate a signal, it asks each of its signals to generate audio and
 * then mixes all of the signals together. <code>SignalChain</code> is fully
 * <code>synchronized</code> so that signals cannot be added and removed from
 * the chain during signal generation.
 * 
 * @author Damien Di Fede
 * 
 */

public class SignalChain implements AudioSignal
{
  // the signals in the order they were added
  private Vector signals;
  // signals we should remove after our next generate
  // this is done so that a signal won't ever be actually 
  // removed in the middle of a generate, which can cause clicks
  private Vector signalsToRemove;
  // all currently enabled signals
  private HashSet enabled;
  // buffers used to generate audio for each signal
  private float[] tmpL;
  private float[] tmpR;

  /**
   * Constructs an empty <code>SignalChain</code>.
   * 
   */
  public SignalChain()
  {
    signals = new Vector();
    signalsToRemove = new Vector();
    enabled = new HashSet();
  }

  /**
   * Adds <code>signal</code> to the end of the chain.
   * 
   * @param signal
   *          the <code>AudioEffect</code> to add
   */
  public synchronized void add(AudioSignal signal)
  {
    signals.add(signal);
    enabled.add(signal);
  }

  /**
   * Removes <code>signal</code> from the chain.
   * 
   * @param signal
   *          the <code>AudioSignal</code> to remove
   */
  public synchronized void remove(AudioSignal signal)
  {
	//Minim.debug("Marking " + signal.toString() + " for removal.");
	signalsToRemove.add(signal);
  }

  /**
   * Removes and returns the <code>i</code><sup>th</sup> signal from the
   * chain.
   * 
   * @param i
   *          the index of the <code>AudioSignal</code> to remove
   * @return the <code>AudioSignal</code> that was removed
   */
  public synchronized AudioSignal remove(int i)
  {
    AudioSignal s = (AudioSignal) signals.remove(i);
    enabled.remove(s);
    return s;
  }

  /**
   * Gets the <code>i<sup>th</sup></code> signal in the chain.
   * 
   * @param i
   *          the index of the <code>AudioSignal</code> to get
   */
  public synchronized AudioSignal get(int i)
  {
    return (AudioSignal) signals.get(i);
  }
  
  /**
   * Returns true if <code>s</code> is in the chain.
   * 
   * @param s the <code>AudioSignal</code> to check for
   * @return true if <code>s</code> is in the chain
   */
  public synchronized boolean contains(AudioSignal s)
  {
    return signals.contains(s);
  }

  /**
   * Enables the <code>i</code><sup>th</sup> effect in the chain.
   * 
   * @param i
   *          the index of the effect to enable
   */
  public synchronized void enable(int i)
  {
    enabled.add(get(i));
  }

  /**
   * Enables <code>signal</code> if it is in the chain.
   * 
   * @param signal
   *          the <code>AudioSignal</code> to enable
   */
  public synchronized void enable(AudioSignal signal)
  {
    if (signals.contains(signal))
    {
      enabled.add(signal);
    }
  }

  /**
   * Enables all signals in the chain.
   * 
   */
  public synchronized void enableAll()
  {
    enabled.addAll(signals);
  }

  /**
   * Returns true if at least one effect in the chain is enabled.
   * 
   * @return true if at least one effect in the chain is enabled
   */
  public synchronized boolean hasEnabled()
  {
    return enabled.size() > 0;
  }

  /**
   * Returns true if <code>e</code> is in the chain and is enabled.
   * 
   * @param signal
   *          the <code>AudioSignal</code> to return the status of
   * @return true if <code>signal</code> is enabled and in the chain
   */
  public synchronized boolean isEnabled(AudioSignal signal)
  {
    return enabled.contains(signal);
  }

  /**
   * Disables the <code>i</code><sup>th</sup> effect in the chain.
   * 
   * @param i
   *          the index of the effect to disable
   */
  public synchronized void disable(int i)
  {
    enabled.remove(get(i));
  }

  /**
   * Disables <code>signal</code> if it is in the chain.
   * 
   * @param signal
   *          the <code>AudioSignal</code> to disable
   */
  public synchronized void disable(AudioSignal signal)
  {
    enabled.remove(signal);
  }

  /**
   * Disables all signals in the chain.
   * 
   */
  public synchronized void disableAll()
  {
    enabled.clear();
  }

  /**
   * Returns the number of signals in the chain.
   * 
   * @return the number of signals in the chain
   */
  public synchronized int size()
  {
    return signals.size();
  }

  /**
   * Removes all signals from the effect chain.
   * 
   */
  public synchronized void clear()
  {
    signals.clear();
    enabled.clear();
  }

  /**
   * Asks all the enabled signals in the chain to generate a new buffer of
   * samples, adds the buffers together and puts the result in
   * <code>signal</code>.
   * 
   */
  public synchronized void generate(float[] signal)
  {
    if ( tmpL == null )
    {
    	tmpL = new float[signal.length];
    }
    for (int i = 0; i < signals.size(); i++)
    {
      AudioSignal s = (AudioSignal) signals.get(i);
      if ( enabled.contains(s) )
      {
        for(int it = 0; it < tmpL.length; it++) 
        { 
        	tmpL[it] = 0; 
        }
        s.generate(tmpL);
        for (int is = 0; is < signal.length; is++)
        {
          signal[is] += tmpL[is];
        }
      }
    }
    // now remove signals we have marked for removal
    signals.removeAll(signalsToRemove);
    signalsToRemove.removeAllElements();    
  }

  /**
   * Asks all the enabled signals in the chain to generate a left and right
   * buffer of samples, adds the signals together and puts the result in
   * <code>left</code> and <code>right</code>.
   */
  public synchronized void generate(float[] left, float[] right)
  {
	  if ( tmpL == null )
	  {
		  tmpL = new float[left.length];
	  }
	  if ( tmpR == null )
	  {
		  tmpR = new float[right.length];
	  }
    for (int i = 0; i < signals.size(); i++)
    {
      AudioSignal s = (AudioSignal) signals.get(i);
      if ( enabled.contains(s) )
      {
        s.generate(tmpL, tmpR);
        for (int j = 0; j < left.length; j++)
        {
          left[j] += tmpL[j];
          right[j] += tmpR[j];
        }
      }
    }
    // now remove signals we have marked for removal
    signals.removeAll(signalsToRemove);
    signalsToRemove.removeAllElements(); 
  }
}
