/*
 * Copyright (c) 2008-2011, Oracle and/or its affiliates. All rights reserved.
 */
package javafx.scene.media;

import java.util.List;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.BooleanPropertyBase;

import javafx.collections.ObservableList;

import com.sun.javafx.beans.annotations.NoBuilder;
import com.sun.javafx.collections.VetoableObservableList;
import com.sun.media.jfxmedia.logging.Logger;

/**
 * The <code>AudioEqualizer</code> class provides audio equalization control for
 * a media player. It contains an {@link ObservableList} of {@link EqualizerBand}
 * elements. Each <code> AudioEqualizer</code> instance is connected to a
 * {@link MediaPlayer} and may be obtained using the
 * {@link MediaPlayer#getAudioEqualizer MediaPlayer.getAudioEqualizer} method.
 *
 * @see MediaPlayer
 * @see EqualizerBand
 */
@NoBuilder
public final class AudioEqualizer {

    /**
     * Maximum number of bands an <code>AudioEqualizer</code> may contain.
     * In the current implementation this value is <code>64</code>.
     */
    public static final int MAX_NUM_BANDS = com.sun.media.jfxmedia.effects.AudioEqualizer.MAX_NUM_BANDS;
    private com.sun.media.jfxmedia.effects.AudioEqualizer jfxEqualizer = null;
    private final ObservableList<EqualizerBand> bands;

    /**
     * ObservableList containing {@link EqualizerBand} elements. The content of
     * the sequence may be changed by adding or removing {@link EqualizerBand}
     * elements. When adding elements, the user must be prepared to catch
     * {@link IllegalArgumentException}s because the internal list is a
     * {@link VetoableObservableList} and any change to the list can be vetoed
     * if a newly added instance is not valid.
     *
     * <p>The constraints for a valid {@link EqualizerBand} instance are:
     * <ul>
     * <li>{@link EqualizerBand#centerFrequencyProperty EqualizerBand.centerFrequency} &gt; 0</li>
     * <li>{@link EqualizerBand#bandwidthProperty EqualizerBand.bandwidth} &gt; 0</li>
     * <li>{@link EqualizerBand#MIN_GAIN EqualizerBand.MIN_GAIN} &le;
     * {@link EqualizerBand#gainProperty EqualizerBand.gain} &le;
     * {@link EqualizerBand#MAX_GAIN EqualizerBand.MAX_GAIN}</li>
     * </ul>
     * </p>
     *
     * <p>The default set of bands is as in the following table; all bands have
     * unity gain (0 dB).
     * <table border="1">
     * <tr><th>Band Index</th><th>Center Frequency (Hz)</th><th>Bandwidth (Hz)</th></tr>
     * <tr><td>0</td><td>32</td><td>19</td></tr>
     * <tr><td>1</td><td>64</td><td>39</td></tr>
     * <tr><td>2</td><td>125</td><td>78</td></tr>
     * <tr><td>3</td><td>250</td><td>156</td></tr>
     * <tr><td>4</td><td>500</td><td>312</td></tr>
     * <tr><td>5</td><td>1000</td><td>625</td></tr>
     * <tr><td>6</td><td>2000</td><td>1250</td></tr>
     * <tr><td>7</td><td>4000</td><td>2500</td></tr>
     * <tr><td>8</td><td>8000</td><td>5000</td></tr>
     * <tr><td>9</td><td>16000</td><td>10000</td></tr>
     * </table>
     *
     * @throws IllegalArgumentException
     * @return ObservableList containing {@link EqualizerBand} elements.
     */
    public final ObservableList<EqualizerBand> getBands() {
        return bands;
    }

    AudioEqualizer() {        
        bands = new Bands();

        // Add reasonable bands
        bands.addAll(new EqualizerBand(32, 19, 0),
                new EqualizerBand(64, 39, 0),
                new EqualizerBand(125, 78, 0),
                new EqualizerBand(250, 156, 0),
                new EqualizerBand(500, 312, 0),
                new EqualizerBand(1000, 625, 0),
                new EqualizerBand(2000, 1250, 0),
                new EqualizerBand(4000, 2500, 0),
                new EqualizerBand(8000, 5000, 0),
                new EqualizerBand(16000, 10000, 0));
    }

    /**
     * Called by NativePlayer when native part is ready
     *
     * @param jfxEqualizer - Instance of native equalizer
     */
    void setAudioEqualizer(com.sun.media.jfxmedia.effects.AudioEqualizer jfxEqualizer) {
        if (jfxEqualizer == null) {
            throw new NullPointerException("jfxEqualizer == null!");
        }

        if (this.jfxEqualizer != null) {
            return;
        }

        this.jfxEqualizer = jfxEqualizer;
        // Propogate enabled
        jfxEqualizer.setEnabled(isEnabled());
        // Propogate bands
        for (EqualizerBand band : bands) {
            if (band.getCenterFrequency() > 0 && band.getBandwidth() > 0) {
                com.sun.media.jfxmedia.effects.EqualizerBand jfxBand =
                        jfxEqualizer.addBand(band.getCenterFrequency(),
                        band.getBandwidth(),
                        band.getGain());
                // setJfxBand will throw an NPE if jfxBand is null which
                // should never happen.
                band.setJfxBand(jfxBand);
            } else {
                Logger.logMsg(Logger.ERROR, "Center frequency [" + band.getCenterFrequency()
                        + "] and bandwidth [" + band.getBandwidth() + "] must be greater than 0.");
            }
        }
    }

    /**
     * Whether the <code>AudioEqualizer</code> is enabled. The default value is
     * <code>true</code>.
     */
    private BooleanProperty enabled;

    /**
     * Enables or disables <code>AudioEqualizer</code>. If the enabled property
     * is set to <tt>false</tt>, {@link AudioEqualizer} settings are preserved but
     * not taken into account during playback, which is equivalent to setting all
     * {@link EqualizerBand#gainProperty EqualizerBand.gain} properties to zero.
     */
    public final void setEnabled(boolean value) {
        enabledProperty().set(value);
    }

    /**
     * Return enabled state of the AudioEqualizer.
     * @return <tt>true</tt> if AudioEqulizer is enabled, <tt>false</tt> otherwise.
     */
    public final boolean isEnabled() {
        return enabled == null ? false : enabled.get();
    }

    public BooleanProperty enabledProperty() {
        if (enabled == null) {
            enabled = new BooleanPropertyBase() {

                @Override
                protected void invalidated() {
                    if (jfxEqualizer != null) {
                        jfxEqualizer.setEnabled(enabled.get());
                    }
                }

                @Override
                public Object getBean() {
                    return AudioEqualizer.this;
                }

                @Override
                public String getName() {
                    return "enabled";
                }
            };
        }
        return enabled;
    }

    private class Bands extends VetoableObservableList<EqualizerBand> {

        @Override
        protected void onProposedChange(List<EqualizerBand> toBeAdded, int[] toBeRemoved) {
            if (jfxEqualizer != null) {
                for (int i = 0; i < toBeRemoved.length; i += 2) {
                    for (EqualizerBand band : subList(toBeRemoved[i], toBeRemoved[i + 1])) {
                        jfxEqualizer.removeBand(band.getCenterFrequency());
                    }
                }

                for (EqualizerBand band : toBeAdded) {
                    if (band.getCenterFrequency() > 0 && band.getBandwidth() > 0) {
                        com.sun.media.jfxmedia.effects.EqualizerBand jfxBand =
                                jfxEqualizer.addBand(band.getCenterFrequency(),
                                band.getBandwidth(),
                                band.getGain());
                        // setJfxBand will throw an NPE if jfxBand is null which
                        // should never happen.
                        band.setJfxBand(jfxBand);
                    } else {
                        Logger.logMsg(Logger.ERROR, "Center frequency [" + band.getCenterFrequency()
                                + "] and bandwidth [" + band.getBandwidth() + "] must be greater than 0.");
                    }
                }
            }
        }
    }
}
