/*
 *  Copyright (C) 2004-2017 Savoir-faire Linux Inc.
 *
 *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
 *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
 *
 *  This program 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.
 *
 *  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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
 */

#include "audiostream.h"
#include "pulselayer.h"
#include "logger.h"
#include "compiler_intrinsics.h"

#include <stdexcept>

namespace ring {

AudioStream::AudioStream(pa_context *c,
                         pa_threaded_mainloop *m,
                         const char *desc,
                         int type,
                         unsigned samplrate,
                         const PaDeviceInfos* infos,
                         bool ec)
    : audiostream_(0), mainloop_(m)
{
    const pa_channel_map channel_map = infos->channel_map;

    pa_sample_spec sample_spec = {
        PA_SAMPLE_S16LE, // PA_SAMPLE_FLOAT32LE,
        samplrate,
        channel_map.channels
    };

    RING_DBG("%s: trying to create stream with device %s (%dHz, %d channels)", desc, infos->name.c_str(), samplrate, channel_map.channels);

    assert(pa_sample_spec_valid(&sample_spec));
    assert(pa_channel_map_valid(&channel_map));

    std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl (pa_proplist_new(), pa_proplist_free);
    pa_proplist_sets(pl.get(), PA_PROP_FILTER_WANT, "echo-cancel");

    audiostream_ = pa_stream_new_with_proplist(c, desc, &sample_spec, &channel_map, ec ? pl.get() : nullptr);
    if (!audiostream_) {
        RING_ERR("%s: pa_stream_new() failed : %s" , desc, pa_strerror(pa_context_errno(c)));
        throw std::runtime_error("Could not create stream\n");
    }

    pa_buffer_attr attributes;
    attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec);
    attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
    attributes.prebuf = 0;
    attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
    attributes.minreq = (uint32_t) -1;

    {
        PulseMainLoopLock lock(mainloop_);
        const pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE);

        if (type == PLAYBACK_STREAM || type == RINGTONE_STREAM) {
            pa_stream_connect_playback(audiostream_,
                    infos->name.empty() ? nullptr : infos->name.c_str(),
                    &attributes,
                    flags,
                    nullptr, nullptr);
        } else if (type == CAPTURE_STREAM) {
            pa_stream_connect_record(audiostream_,
                    infos->name.empty() ? nullptr : infos->name.c_str(),
                    &attributes,
                    flags);
        }
    }

    pa_stream_set_state_callback(audiostream_, [](pa_stream* s, void* user_data){
        static_cast<AudioStream*>(user_data)->stateChanged(s);
    }, this);
    pa_stream_set_moved_callback(audiostream_, [](pa_stream* s, void* user_data){
        static_cast<AudioStream*>(user_data)->moved(s);
    }, this);
}

AudioStream::~AudioStream()
{
    PulseMainLoopLock lock(mainloop_);

    pa_stream_disconnect(audiostream_);

    // make sure we don't get any further callback
    pa_stream_set_state_callback(audiostream_, NULL, NULL);
    pa_stream_set_write_callback(audiostream_, NULL, NULL);
    pa_stream_set_read_callback(audiostream_, NULL, NULL);
    pa_stream_set_moved_callback(audiostream_, NULL, NULL);
    pa_stream_set_underflow_callback(audiostream_, NULL, NULL);
    pa_stream_set_overflow_callback(audiostream_, NULL, NULL);

    pa_stream_unref(audiostream_);
}

void AudioStream::moved(pa_stream* s)
{
    audiostream_ = s;
    RING_DBG("Stream %d to %s", pa_stream_get_index(s), pa_stream_get_device_name(s));
}

void
AudioStream::stateChanged(pa_stream* s)
{
    UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX];

    switch (pa_stream_get_state(s)) {
        case PA_STREAM_CREATING:
            RING_DBG("Stream is creating...");
            break;

        case PA_STREAM_TERMINATED:
            RING_DBG("Stream is terminating...");
            break;

        case PA_STREAM_READY:
            RING_DBG("Stream successfully created, connected to %s", pa_stream_get_device_name(s));
            //RING_DBG("maxlength %u", pa_stream_get_buffer_attr(s)->maxlength);
            //RING_DBG("tlength %u", pa_stream_get_buffer_attr(s)->tlength);
            //RING_DBG("prebuf %u", pa_stream_get_buffer_attr(s)->prebuf);
            //RING_DBG("minreq %u", pa_stream_get_buffer_attr(s)->minreq);
            //RING_DBG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize);
            //RING_DBG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s)));
            break;

        case PA_STREAM_UNCONNECTED:
            RING_DBG("Stream unconnected");
            break;

        case PA_STREAM_FAILED:
        default:
            RING_ERR("Stream failure: %s" , pa_strerror(pa_context_errno(pa_stream_get_context(s))));
            break;
    }
}

bool AudioStream::isReady()
{
    if (!audiostream_)
        return false;

    return pa_stream_get_state(audiostream_) == PA_STREAM_READY;
}

} // namespace ring
