/*
  ==============================================================================

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

  ==============================================================================
*/

#include "../../../../juce_core/basics/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE

#include "juce_AudioDeviceSelectorComponent.h"
#include "../buttons/juce_TextButton.h"
#include "../menus/juce_PopupMenu.h"
#include "../windows/juce_AlertWindow.h"
#include "../lookandfeel/juce_LookAndFeel.h"
#include "../../../../juce_core/text/juce_LocalisedStrings.h"


//==============================================================================
class MidiInputListBox  : public SimpleListBox,
                          public SimpleListBoxModel
{
public:
    //==============================================================================
    MidiInputListBox (AudioDeviceManager& deviceManager_)
        : SimpleListBox (T("midi"), 0),
          deviceManager (deviceManager_)
    {
        devices = MidiInput::getDevices();

        setModel (this);
        setOutlineColour (getLookAndFeel().comboBoxOutline, 1);
    }

    ~MidiInputListBox()
    {
    }

    int getNumRows()
    {
        return devices.size();
    }

    void paintListBoxItem (int row,
                           Graphics& g,
                           int width, int height,
                           bool rowIsSelected)
    {
        if (row >= 0 && row < devices.size())
        {
            if (rowIsSelected)
                g.fillAll (getLookAndFeel().textEditorHighlight.withMultipliedAlpha (0.3f));

            const String device (devices [row]);

            const bool enabled = deviceManager.isMidiInputEnabled (device);
            const int x = getTickX();
            const int tickW = height - height / 4;

            getLookAndFeel().drawTickBox (g, x - tickW, (height - tickW) / 2, tickW, tickW,
                                          enabled, true, true, false);

            g.setFont (height * 0.6f);
            g.setColour (Colours::black.withAlpha (enabled ? 1.0f : 0.6f));
            g.drawText (device, x, 0, width - x - 2, height, Justification::centredLeft, true);
        }
    }

    void listBoxItemClicked (int row, const MouseEvent& e)
    {
        selectRow (row);

        if (e.x < getTickX())
            flipEnablement (row);
    }

    void listBoxItemDoubleClicked (int row, const MouseEvent&)
    {
        flipEnablement (row);
    }

    void paint (Graphics& g)
    {
        SimpleListBox::paint (g);

        if (getNumRows() == 0)
        {
            g.setColour (Colours::grey);
            g.setFont (13.0f);
            g.drawText (TRANS("(no midi inputs available)"),
                        0, 0, getWidth(), getHeight() / 2, Justification::centred, true);
        }
    }

    //==============================================================================
    juce_UseDebuggingNewOperator

private:
    AudioDeviceManager& deviceManager;
    StringArray devices;

    void flipEnablement (const int row)
    {
        if (row >= 0 && row < devices.size())
        {
            const String device (devices [row]);

            deviceManager.setMidiInputEnabled (device, ! deviceManager.isMidiInputEnabled (device));
        }
    }

    int getTickX() const throw()
    {
        return getRowHeight() + 5;
    }

    MidiInputListBox (const MidiInputListBox&);
    const MidiInputListBox& operator= (const MidiInputListBox&);
};

//==============================================================================
AudioDeviceSelectorComponent::AudioDeviceSelectorComponent (AudioDeviceManager& deviceManager_,
                                                            const int minInputChannels_,
                                                            const int maxInputChannels_,
                                                            const int minOutputChannels_,
                                                            const int maxOutputChannels_,
                                                            const bool showMidiOptions_)
    : deviceManager (deviceManager_),
      minOutputChannels (minOutputChannels_),
      maxOutputChannels (maxOutputChannels_),
      minInputChannels (minInputChannels_),
      maxInputChannels (maxInputChannels_),
      showMidiOptions (showMidiOptions_),
      sampleRateDropDown (0),
      inputChansDropDown (0),
      inputsLabel (0),
      outputChansDropDown (0),
      outputsLabel (0),
      sampleRateLabel (0),
      bufferSizeDropDown (0),
      bufferSizeLabel (0),
      launchUIButton (0)
{
    jassert (minOutputChannels >= 0 && minOutputChannels <= maxOutputChannels);
    jassert (minInputChannels >= 0 && minInputChannels <= maxInputChannels);

    audioDeviceDropDown = new ComboBox (T("device"));
    deviceManager_.addDeviceNamesToComboBox (*audioDeviceDropDown);
    audioDeviceDropDown->setSelectedId (-1);

    if (deviceManager_.getCurrentAudioDeviceName().isNotEmpty())
        audioDeviceDropDown->setText (deviceManager_.getCurrentAudioDeviceName());

    audioDeviceDropDown->addActionListener (this);
    addAndMakeVisible (audioDeviceDropDown);

    Label* label = new Label (T("l1"), TRANS ("audio device:"));
    label->attachToComponent (audioDeviceDropDown, true);

    if (showMidiOptions)
    {
        addAndMakeVisible (midiInputsList = new MidiInputListBox (deviceManager));

        midiInputsLabel = new Label (T("lm"), TRANS ("active midi inputs:"));
        midiInputsLabel->setJustificationType (Justification::topRight);
        midiInputsLabel->attachToComponent (midiInputsList, true);
    }
    else
    {
        midiInputsList = 0;
        midiInputsLabel = 0;
    }

    deviceManager_.addChangeListener (this);
    changeListenerCallback (0);
}

AudioDeviceSelectorComponent::~AudioDeviceSelectorComponent()
{
    deviceManager.removeChangeListener (this);
    deleteAllChildren();
}

void AudioDeviceSelectorComponent::resized()
{
    int lx = proportionOfWidth (0.35f);
    int w = proportionOfWidth (0.55f);
    int y = 15;
    const int h = 24;
    const int dh = 30;

    audioDeviceDropDown->setBounds (lx, y, w, h);
    y += dh;

    if (sampleRateDropDown != 0)
    {
        sampleRateDropDown->setBounds (lx, y, w, h);
        y += dh;
    }

    if (bufferSizeDropDown != 0)
    {
        bufferSizeDropDown->setBounds (lx, y, w, h);
        y += dh;
    }

    if (outputChansDropDown != 0)
    {
        outputChansDropDown->setBounds (lx, y, w, h);
        y += dh;
    }

    if (inputChansDropDown != 0)
    {
        inputChansDropDown->setBounds (lx, y, w, h);
        y += dh;
    }

    if (launchUIButton != 0)
    {
        launchUIButton->setBounds (lx, y, 150, h);
        ((TextButton*) launchUIButton)->changeWidthToFitText();
        y += dh;
    }

    if (midiInputsList != 0)
    {
        midiInputsList->setBounds (lx, y, w, jmin (h * 4, getHeight() - y - 2));
        y += dh;
    }
}

void AudioDeviceSelectorComponent::buttonClicked (Button*)
{
    AudioIODevice* const device = deviceManager.getCurrentAudioDevice();

    if (device != 0 && device->hasControlPanel())
    {
        const String lastDevice (device->getName());

        if (device->showControlPanel())
        {
            deviceManager.setAudioDevice (String::empty);
            deviceManager.setAudioDevice (lastDevice);
        }

        getTopLevelComponent()->toFront (true);
    }
}

void AudioDeviceSelectorComponent::actionListenerCallback (const String& m)
{
    AudioIODevice* const audioDevice = deviceManager.getCurrentAudioDevice();

    if (m == audioDeviceDropDown->getName())
    {
        if (audioDeviceDropDown->getSelectedId() < 0)
        {
            deviceManager.setAudioDevice (String::empty);
        }
        else
        {
            String error (deviceManager.setAudioDevice (audioDeviceDropDown->getText()));

            if (error.isNotEmpty())
            {
                AlertWindow::showMessageBox (AlertWindow::WarningIcon,
                                             T("Error while opening \"")
                                                + audioDeviceDropDown->getText()
                                                + T("\""),
                                             error);
            }
        }

        if (deviceManager.getCurrentAudioDeviceName().isNotEmpty())
            audioDeviceDropDown->setText (deviceManager.getCurrentAudioDeviceName());
        else
            audioDeviceDropDown->setSelectedId (-1);
    }
    else if (audioDevice != 0)
    {
        if (bufferSizeDropDown != 0 && m == bufferSizeDropDown->getName())
        {
            if (bufferSizeDropDown->getSelectedId() > 0)
                deviceManager.setAudioDevice (audioDevice->getName(),
                                              bufferSizeDropDown->getSelectedId(),
                                              audioDevice->getCurrentSampleRate());
        }
        else if (sampleRateDropDown != 0 && m == sampleRateDropDown->getName())
        {
            if (sampleRateDropDown->getSelectedId() > 0)
                deviceManager.setAudioDevice (audioDevice->getName(),
                                              audioDevice->getCurrentBufferSizeSamples(),
                                              sampleRateDropDown->getSelectedId());
        }
        else if (outputChansDropDown != 0 && m == outputChansDropDown->getName())
        {
            const int bit = (outputChansDropDown->getSelectedId() - 1) * maxOutputChannels;

            BitArray chans;
            for (int i = 0; i < maxOutputChannels; ++i)
                chans.setBit (bit + i);

            deviceManager.setAudioDevice (audioDevice->getName(),
                                          audioDevice->getCurrentBufferSizeSamples(),
                                          audioDevice->getCurrentSampleRate(),
                                          0, &chans);
        }
        else if (inputChansDropDown != 0 && m == inputChansDropDown->getName())
        {
            const int bit = (inputChansDropDown->getSelectedId() - 1) * maxInputChannels;

            BitArray chans;
            for (int i = 0; i < maxInputChannels; ++i)
                chans.setBit (bit + i);

            deviceManager.setAudioDevice (audioDevice->getName(),
                                          audioDevice->getCurrentBufferSizeSamples(),
                                          audioDevice->getCurrentSampleRate(),
                                          &chans, 0);
        }
    }
}

void AudioDeviceSelectorComponent::changeListenerCallback (void*)
{
    deleteAndZero (sampleRateDropDown);
    deleteAndZero (inputChansDropDown);
    deleteAndZero (inputsLabel);
    deleteAndZero (outputChansDropDown);
    deleteAndZero (outputsLabel);
    deleteAndZero (sampleRateLabel);
    deleteAndZero (bufferSizeDropDown);
    deleteAndZero (bufferSizeLabel);
    deleteAndZero (launchUIButton);

    AudioIODevice* const currentDevice = deviceManager.getCurrentAudioDevice();

    if (currentDevice != 0)
    {
        // sample rate
        addAndMakeVisible (sampleRateDropDown = new ComboBox (T("samplerate")));
        sampleRateLabel = new Label (T("l2"), TRANS ("sample rate:"));
        sampleRateLabel->attachToComponent (sampleRateDropDown, true);

        const int numRates = currentDevice->getNumSampleRates();

        int i;
        for (i = 0; i < numRates; ++i)
        {
            const int rate = roundDoubleToInt (currentDevice->getSampleRate (i));
            sampleRateDropDown->addItem (String (rate) + T(" Hz"), rate);
        }

        const double currentRate = currentDevice->getCurrentSampleRate();
        sampleRateDropDown->setSelectedId (roundDoubleToInt (currentRate));
        sampleRateDropDown->addActionListener (this);

        // buffer size
        addAndMakeVisible (bufferSizeDropDown = new ComboBox (T("buffersize")));
        bufferSizeLabel = new Label (T("l2"), TRANS ("audio buffer size:"));
        bufferSizeLabel->attachToComponent (bufferSizeDropDown, true);

        const int numBufferSizes = currentDevice->getNumBufferSizesAvailable();

        for (i = 0; i < numBufferSizes; ++i)
        {
            const int bs = currentDevice->getBufferSizeSamples (i);
            bufferSizeDropDown->addItem (String (bs)
                                          + T(" samples (")
                                          + String (bs * 1000.0 / currentRate, 1)
                                          + T(" ms)"),
                                         bs);
        }

        bufferSizeDropDown->setSelectedId (currentDevice->getCurrentBufferSizeSamples());
        bufferSizeDropDown->addActionListener (this);

        // output chans
        if (maxOutputChannels > 0)
        {
            addAndMakeVisible (outputChansDropDown = new ComboBox (T("outs")));
            outputsLabel = new Label (T("l3"), TRANS ("output channels:"));
            outputsLabel->attachToComponent (outputChansDropDown, true);

            StringArray outs (currentDevice->getOutputChannelNames());

            if (outs.size() == 0)
            {
                outputChansDropDown->addItem (TRANS ("<< none available >>"), -1);
            }
            else
            {
                if (minOutputChannels == 0)
                {
                    outputChansDropDown->addItem (T("<< none >>"), -1);
                    outputChansDropDown->addSeparator();
                }

                for (int i = 0; i < outs.size() / maxOutputChannels; ++i)
                {
                    String name;

                    for (int j = 0; j < maxOutputChannels; ++j)
                    {
                        name += outs [i * maxOutputChannels + j];

                        if (j < maxOutputChannels - 1)
                            name += T(" & ");
                    }

                    outputChansDropDown->addItem (name, i + 1);
                }
            }

            const int lowestBit = deviceManager.getOutputChannels().findNextSetBit (0);
            outputChansDropDown->setSelectedId ((lowestBit < 0) ? -1 : lowestBit / maxOutputChannels + 1);
            outputChansDropDown->addActionListener (this);
        }

        // input chans
        if (maxInputChannels > 0)
        {
            addAndMakeVisible (inputChansDropDown = new ComboBox (T("ins")));
            inputsLabel = new Label (T("l4"), TRANS("input channels:"));
            inputsLabel->attachToComponent (inputChansDropDown, true);

            StringArray ins (currentDevice->getInputChannelNames());

            if (ins.size() == 0)
            {
                inputChansDropDown->addItem (T("<< none available >>"), -1);
            }
            else
            {
                if (minInputChannels == 0)
                {
                    inputChansDropDown->addItem (T("<< none >>"), -1);
                    inputChansDropDown->addSeparator();
                }

                for (int i = 0; i < ins.size() / maxInputChannels; ++i)
                {
                    String name;

                    for (int j = 0; j < maxInputChannels; ++j)
                    {
                        name += ins [i * maxInputChannels + j];

                        if (j < maxInputChannels - 1)
                            name += T(" & ");
                    }

                    inputChansDropDown->addItem (name, i + 1);
                }
            }

            const int lowestBit = deviceManager.getInputChannels().findNextSetBit (0);
            inputChansDropDown->setSelectedId ((lowestBit < 0) ? -1 : lowestBit / maxInputChannels + 1);
            inputChansDropDown->addActionListener (this);
        }

        if (currentDevice->hasControlPanel())
        {
            addAndMakeVisible (launchUIButton = new TextButton (T("show this device's control panel"),
                                                                T("opens the device's own control panel")));

            launchUIButton->addButtonListener (this);
        }

        if (midiInputsList != 0)
            midiInputsList->updateContent();
    }
    else
    {
        audioDeviceDropDown->setSelectedId (-1);
    }

    resized();
}

END_JUCE_NAMESPACE
