/**
 * @file
 * @brief Example code to list available devices and play a test sound
 * @author Jonathan Thomas <jonathan@openshot.org>
 * @author FeRD (Frank Dana) <ferdnyc@gmail.com>
 *
 * Copyright (c) 2008-2020 OpenShot Studios, LLC
 * <http://www.openshotstudios.com/>. This file is part of OpenShot Audio
 * Library (libopenshot-audio), an open-source project dedicated to delivering
 * high quality audio editing and playback solutions to the world. For more
 * information visit <http://www.openshot.org/>.
 *
 * OpenShot Audio Library (libopenshot-audio) is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General
 * Public License as published by the Free Software Foundation, either version
 * 3 of the License, or (at your option) any later version.
 *
 * OpenShot Audio Library (libopenshot-audio) 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 OpenShot Library. If not, see <http://www.gnu.org/licenses/>.
 *
 * @mainpage OpenShot Audio Library C++ API
 *
 * Welcome to the OpenShot Audio Library C++ API.
 * This library is used by libopenshot to enable audio features, which powers the
 * <a href="http://www.openshot.org">OpenShot Video Editor</a> application.
 */

#include <iostream>
#include <thread>
#include <chrono>

// Avoid an implicit (and deprecated) 'using namespace juce;'
#ifndef DONT_SET_USING_JUCE_NAMESPACE
    #define DONT_SET_USING_JUCE_NAMESPACE 1
#endif
#include "OpenShotAudio.h"

// Subclass the AudioDeviceManager so we can skip WASAPI devices on Windows
class TestAudioDeviceManager : public juce::AudioDeviceManager {
public:
    static void addIfNotNull (
        juce::OwnedArray<juce::AudioIODeviceType>& list,
        juce::AudioIODeviceType* const device)
    {
        if (device != nullptr)
            list.add (device);
    }

    void createAudioDeviceTypes (juce::OwnedArray<juce::AudioIODeviceType>& list) override
    {
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_WASAPI (juce::WASAPIDeviceMode::shared));
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_WASAPI (juce::WASAPIDeviceMode::exclusive));
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_WASAPI (juce::WASAPIDeviceMode::sharedLowLatency));
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_DirectSound());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_ASIO());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_CoreAudio());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_iOSAudio());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_Bela());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_ALSA());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_JACK());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_Oboe());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_OpenSLES());
        addIfNotNull (list, juce::AudioIODeviceType::createAudioIODeviceType_Android());
    }
};


void display_device_probe(TestAudioDeviceManager& deviceManager) {
    std::cout << "Audio device probe results:\n";
    int device_count = 0;
    for (const auto& t : deviceManager.getAvailableDeviceTypes()) {
        juce::String typeString;
        typeString << "Type " << t->getTypeName() << ":";
        std::cout << typeString << std::endl;
        auto deviceNames = t->getDeviceNames();
        device_count += deviceNames.size();
        for (const auto deviceName : deviceNames) {
            std::cout << "  - " << deviceName << std::endl;
        }
    }
    std::cout << "Discovered " << device_count << " total audio devices.\n\n";

    const auto* curType = deviceManager.getCurrentDeviceTypeObject();
    auto* curDevice = deviceManager.getCurrentAudioDevice();

    std::cout << "Current audio device: ";
    if (curType == nullptr || curDevice == nullptr) {
        std::cout << "<none>\n";
    } else {
        std::cout << curType->getTypeName()
                  << " - " << curDevice->getName().quoted() << '\n';
        std::cout << "Sample rate: "
                  << curDevice->getCurrentSampleRate() << " Hz\n";
        std::cout << "Block size: "
                  << curDevice->getCurrentBufferSizeSamples() << " samples\n";
        std::cout << "Bit depth: "
                  << curDevice->getCurrentBitDepth() << '\n';
    }
}


int main(int argc, char* argv[])
{
    juce::ignoreUnused (argc, argv);

    // Initialize default audio device
    std::cout << "Initialising audio playback device.\n";
    TestAudioDeviceManager deviceManager;

    auto error = deviceManager.initialise (
        0,        // numInputChannelsNeeded
        2,        // numOutputChannelsNeeded
        nullptr,  // savedState
        true      // selectDefaultDeviceOnFailure
    );

    // Output error (if any)
    if (error.isNotEmpty()) {
        std::cout << "Error on initialise(): " << error.toStdString() << std::endl;
        exit(-1);
    }

    // Show detected audio devices
    display_device_probe(deviceManager);

    // Pause to take all that in
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // Play test sound

    const int pause(2);
    using stride = std::chrono::duration<double, std::ratio<pause, 1>>;

    std::cout << "\nYou should hear five test tones, precisely "
              << pause << " second" << (pause > 1 ? "s" : "")
              << " apart.\n";

    auto start = std::chrono::steady_clock::now();

    for (auto s = static_cast<stride>(1); s < static_cast<stride>(6); ++s) {
        std::cout << s.count() << (s.count() < 5 ? "... " : ".") << std::flush;
        deviceManager.playTestSound();
        std::this_thread::sleep_until(start + s);
    }

    std::cout << "\nDemo complete. Shutting down device manager.\n";

    // Stop audio devices
    deviceManager.closeAudioDevice();
    deviceManager.removeAllChangeListeners();
    deviceManager.dispatchPendingMessages();

    return 0;
}
