/*
 * EqualizerUI.
 * 
 * JavaZOOM : jlgui@javazoom.net
 *            http://www.javazoom.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 javazoom.jlgui.player.amp.equalizer.ui;

import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javazoom.jlgui.player.amp.PlayerActionEvent;
import javazoom.jlgui.player.amp.PlayerUI;
import javazoom.jlgui.player.amp.skin.AbsoluteLayout;
import javazoom.jlgui.player.amp.skin.DropTargetAdapter;
import javazoom.jlgui.player.amp.skin.ImageBorder;
import javazoom.jlgui.player.amp.skin.Skin;
import javazoom.jlgui.player.amp.util.Config;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class implements an equalizer UI.
 * <p/>
 * The equalizer consists of 32 band-pass filters.
 * Each band of the equalizer can take on a fractional value between
 * -1.0 and +1.0.
 * At -1.0, the input signal is attenuated by 6dB, at +1.0 the signal is
 * amplified by 6dB.
 */
public class EqualizerUI extends JPanel implements ActionListener, ChangeListener
{
    private static Log log = LogFactory.getLog(EqualizerUI.class);
    private int minGain = 0;
    private int maxGain = 100;
    private int[] gainValue = { 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 };
    private int[] PRESET_NORMAL = { 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 };
    private int[] PRESET_CLASSICAL = { 50, 50, 50, 50, 50, 50, 70, 70, 70, 76 };
    private int[] PRESET_CLUB = { 50, 50, 42, 34, 34, 34, 42, 50, 50, 50 };
    private int[] PRESET_DANCE = { 26, 34, 46, 50, 50, 66, 70, 70, 50, 50 };
    private int[] PRESET_FULLBASS = { 26, 26, 26, 36, 46, 62, 76, 78, 78, 78 };
    private int[] PRESET_FULLBASSTREBLE = { 34, 34, 50, 68, 62, 46, 28, 22, 18, 18 };
    private int[] PRESET_FULLTREBLE = { 78, 78, 78, 62, 42, 24, 8, 8, 8, 8 };
    private int[] PRESET_LAPTOP = { 38, 22, 36, 60, 58, 46, 38, 24, 16, 14 };
    private int[] PRESET_LIVE = { 66, 50, 40, 36, 34, 34, 40, 42, 42, 42 };
    private int[] PRESET_PARTY = { 32, 32, 50, 50, 50, 50, 50, 50, 32, 32 };
    private int[] PRESET_POP = { 56, 38, 32, 30, 38, 54, 56, 56, 54, 54 };
    private int[] PRESET_REGGAE = { 48, 48, 50, 66, 48, 34, 34, 48, 48, 48 };
    private int[] PRESET_ROCK = { 32, 38, 64, 72, 56, 40, 28, 24, 24, 24 };
    private int[] PRESET_TECHNO = { 30, 34, 48, 66, 64, 48, 30, 24, 24, 28 };
    private Config config = null;
    private PlayerUI player = null;
    private Skin ui = null;
    private JPopupMenu mainpopup = null;
    public static final int LINEARDIST = 1;
    public static final int OVERDIST = 2;
    private float[] bands = null;
    private int[] eqgains = null;
    private int eqdist = OVERDIST;

    public EqualizerUI()
    {
        super();
        setDoubleBuffered(true);
        config = Config.getInstance();
        eqgains = new int[10];
        setLayout(new AbsoluteLayout());
        int[] vals = config.getLastEqualizer();
        if (vals != null)
        {
            for (int h = 0; h < vals.length; h++)
            {
                gainValue[h] = vals[h];
            }
        }
        // DnD support disabled.
        DropTargetAdapter dnd = new DropTargetAdapter()
        {
            public void processDrop(Object data)
            {
                return;
            }
        };
        DropTarget dt = new DropTarget(this, DnDConstants.ACTION_COPY, dnd, false);        
    }

    /**
     * Return skin.
     * @return
     */
    public Skin getSkin()
    {
        return ui;
    }

    /**
     * Set skin.
     * @param ui
     */
    public void setSkin(Skin ui)
    {
        this.ui = ui;
    }

    /**
     * Set parent player.
     * @param mp
     */
    public void setPlayer(PlayerUI mp)
    {
        player = mp;
    }

    public void loadUI()
    {
        log.info("Load EqualizerUI (EDT=" + SwingUtilities.isEventDispatchThread() + ")");
        removeAll();
        // Background
        ImageBorder border = new ImageBorder();
        border.setImage(ui.getEqualizerImage());
        setBorder(border);
        // On/Off
        add(ui.getAcEqOnOff(), ui.getAcEqOnOff().getConstraints());
        ui.getAcEqOnOff().removeActionListener(this);
        ui.getAcEqOnOff().addActionListener(this);
        // Auto
        add(ui.getAcEqAuto(), ui.getAcEqAuto().getConstraints());
        ui.getAcEqAuto().removeActionListener(this);
        ui.getAcEqAuto().addActionListener(this);
        // Sliders
        add(ui.getAcEqPresets(), ui.getAcEqPresets().getConstraints());
        for (int i = 0; i < ui.getAcEqSliders().length; i++)
        {
            add(ui.getAcEqSliders()[i], ui.getAcEqSliders()[i].getConstraints());
            ui.getAcEqSliders()[i].setValue(maxGain - gainValue[i]);
            ui.getAcEqSliders()[i].removeChangeListener(this);
            ui.getAcEqSliders()[i].addChangeListener(this);
        }
        if (ui.getSpline() != null)
        {
            ui.getSpline().setValues(gainValue);
            add(ui.getSpline(), ui.getSpline().getConstraints());
        }
        // Popup menu on TitleBar
        mainpopup = new JPopupMenu();
        String[] presets = { "Normal", "Classical", "Club", "Dance", "Full Bass", "Full Bass & Treble", "Full Treble", "Laptop", "Live", "Party", "Pop", "Reggae", "Rock", "Techno" };
        JMenuItem mi;
        for (int p = 0; p < presets.length; p++)
        {
            mi = new JMenuItem(presets[p]);
            mi.removeActionListener(this);
            mi.addActionListener(this);
            mainpopup.add(mi);
        }
        ui.getAcEqPresets().removeActionListener(this);
        ui.getAcEqPresets().addActionListener(this);
        validate();
    }

    /* (non-Javadoc)
     * @see javax.swing.event.ChangeListener#stateChanged(javax.swing.event.ChangeEvent)
     */
    public void stateChanged(ChangeEvent e)
    {
        for (int i = 0; i < ui.getAcEqSliders().length; i++)
        {
            gainValue[i] = maxGain - ui.getAcEqSliders()[i].getValue();
        }
        if (ui.getSpline() != null) ui.getSpline().repaint();
        // Apply equalizer values.
        synchronizeEqualizer();
    }

    /**
     * Set bands array for equalizer.
     *
     * @param bands
     */
    public void setBands(float[] bands)
    {
        this.bands = bands;
    }

    /**
     * Apply equalizer function.
     *
     * @param gains
     * @param min
     * @param max
     */
    public void updateBands(int[] gains, int min, int max)
    {
        if ((gains != null) && (bands != null))
        {
            int j = 0;
            float gvalj = (gains[j] * 2.0f / (max - min) * 1.0f) - 1.0f;
            float gvalj1 = (gains[j + 1] * 2.0f / (max - min) * 1.0f) - 1.0f;
            // Linear distribution : 10 values => 32 values.
            if (eqdist == LINEARDIST)
            {
                float a = (gvalj1 - gvalj) * 1.0f;
                float b = gvalj * 1.0f - (gvalj1 - gvalj) * j;
                // x=s*x'
                float s = (gains.length - 1) * 1.0f / (bands.length - 1) * 1.0f;
                for (int i = 0; i < bands.length; i++)
                {
                    float ind = s * i;
                    if (ind > (j + 1))
                    {
                        j++;
                        gvalj = (gains[j] * 2.0f / (max - min) * 1.0f) - 1.0f;
                        gvalj1 = (gains[j + 1] * 2.0f / (max - min) * 1.0f) - 1.0f;
                        a = (gvalj1 - gvalj) * 1.0f;
                        b = gvalj * 1.0f - (gvalj1 - gvalj) * j;
                    }
                    // a*x+b
                    bands[i] = a * i * 1.0f * s + b;
                }
            }
            // Over distribution : 10 values => 10 first value of 32 values.
            else if (eqdist == OVERDIST)
            {
                for (int i = 0; i < gains.length; i++)
                {
                    bands[i] = (gains[i] * 2.0f / (max - min) * 1.0f) - 1.0f;
                }
            }
        }
    }

    /* (non-Javadoc)
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e)
    {
        String cmd = e.getActionCommand();
        log.debug("Action=" + cmd + " (EDT=" + SwingUtilities.isEventDispatchThread() + ")");
        // On/Off
        if (cmd.equals(PlayerActionEvent.ACEQONOFF))
        {
            if (ui.getAcEqOnOff().isSelected())
            {
                config.setEqualizerOn(true);
            }
            else
            {
                config.setEqualizerOn(false);
            }
            synchronizeEqualizer();
        }
        // Auto
        else if (cmd.equals(PlayerActionEvent.ACEQAUTO))
        {
            if (ui.getAcEqAuto().isSelected())
            {
                config.setEqualizerAuto(true);
            }
            else
            {
                config.setEqualizerAuto(false);
            }
        }
        // Presets
        else if (cmd.equals(PlayerActionEvent.ACEQPRESETS))
        {
            if (e.getModifiers() == MouseEvent.BUTTON1_MASK)
            {
                mainpopup.show(this, ui.getAcEqPresets().getLocation().x, ui.getAcEqPresets().getLocation().y);
            }
        }
        else if (cmd.equals("Normal"))
        {
            updateSliders(PRESET_NORMAL);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Classical"))
        {
            updateSliders(PRESET_CLASSICAL);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Club"))
        {
            updateSliders(PRESET_CLUB);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Dance"))
        {
            updateSliders(PRESET_DANCE);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Full Bass"))
        {
            updateSliders(PRESET_FULLBASS);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Full Bass & Treble"))
        {
            updateSliders(PRESET_FULLBASSTREBLE);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Full Treble"))
        {
            updateSliders(PRESET_FULLTREBLE);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Laptop"))
        {
            updateSliders(PRESET_LAPTOP);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Live"))
        {
            updateSliders(PRESET_LIVE);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Party"))
        {
            updateSliders(PRESET_PARTY);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Pop"))
        {
            updateSliders(PRESET_POP);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Reggae"))
        {
            updateSliders(PRESET_REGGAE);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Rock"))
        {
            updateSliders(PRESET_ROCK);
            synchronizeEqualizer();
        }
        else if (cmd.equals("Techno"))
        {
            updateSliders(PRESET_TECHNO);
            synchronizeEqualizer();
        }
    }

    /**
     * Update sliders from gains array.
     *
     * @param gains
     */
    public void updateSliders(int[] gains)
    {
        if (gains != null)
        {
            for (int i = 0; i < gains.length; i++)
            {
                gainValue[i + 1] = gains[i];
                ui.getAcEqSliders()[i + 1].setValue(maxGain - gainValue[i + 1]);
            }
        }
    }

    /**
     * Apply equalizer values.
     */
    public void synchronizeEqualizer()
    {
        config.setLastEqualizer(gainValue);
        if (config.isEqualizerOn())
        {
            for (int j = 0; j < eqgains.length; j++)
            {
                eqgains[j] = -gainValue[j + 1] + maxGain;
            }
            updateBands(eqgains, minGain, maxGain);
        }
        else
        {
            for (int j = 0; j < eqgains.length; j++)
            {
                eqgains[j] = (maxGain - minGain) / 2;
            }
            updateBands(eqgains, minGain, maxGain);
        }
    }

    /**
     * Return equalizer bands distribution.
     * @return
     */
    public int getEqdist()
    {
        return eqdist;
    }

    /**
     * Set equalizer bands distribution.
     * @param i
     */
    public void setEqdist(int i)
    {
        eqdist = i;
    }

    /**
     * Simulates "On/Off" selection.
     */
    public void pressOnOff()
    {
        ui.getAcEqOnOff().doClick();
    }

    /**
     * Simulates "Auto" selection.
     */
    public void pressAuto()
    {
        ui.getAcEqAuto().doClick();
    }
}
