#include "record_audio_dshow.h"
#include "headers_ffmpeg.h"

#include "error_define.h"
#include "log_helper.h"

namespace am {
record_audio_dshow::record_audio_dshow()
{
    ffmpeg_register_all();
    ffmpeg_register_devices();

    _fmt_ctx = NULL;
    _input_fmt = NULL;
    _codec_ctx = NULL;
    _codec = NULL;

    _stream_index = -1;
}

record_audio_dshow::~record_audio_dshow()
{
    stop();
    cleanup();
}

int record_audio_dshow::init(const std::string &device_name,
                             const std::string &device_id,
                             bool is_input)
{
    int error = AE_NO;
    int ret = 0;

    if (_inited == true)
        return error;

    do {
        _device_name = device_name;
        _device_id = device_id;
        _is_input = is_input;

        _input_fmt = av_find_input_format("dshow");
        if (!_input_fmt) {
            error = AE_FFMPEG_FIND_INPUT_FMT_FAILED;
            break;
        }

        _fmt_ctx = avformat_alloc_context();
        ret = avformat_open_input(&_fmt_ctx, _device_name.c_str(), _input_fmt, NULL);
        if (ret != 0) {
            error = AE_FFMPEG_OPEN_INPUT_FAILED;
            break;
        }

        ret = avformat_find_stream_info(_fmt_ctx, NULL);
        if (ret < 0) {
            error = AE_FFMPEG_FIND_STREAM_FAILED;
            break;
        }

        int stream_index = -1;
        for (int i = 0; i < _fmt_ctx->nb_streams; i++) {
            if (ffmpeg_get_codec_type(_fmt_ctx->streams[i]) == AVMEDIA_TYPE_AUDIO) {
                stream_index = i;
                break;
            }
        }

        if (stream_index == -1) {
            error = AE_FFMPEG_FIND_STREAM_FAILED;
            break;
        }

        _stream_index = stream_index;
        _codec_ctx = ffmpeg_get_codec_context(_fmt_ctx->streams[stream_index]);
        _codec = (AVCodec*)avcodec_find_decoder(_codec_ctx->codec_id);
        if (_codec == NULL) {
            error = AE_FFMPEG_FIND_DECODER_FAILED;
            break;
        }

        ret = avcodec_open2(_codec_ctx, _codec, NULL);
        if (ret != 0) {
            error = AE_FFMPEG_OPEN_CODEC_FAILED;
            break;
        }

        _inited = true;

        _sample_rate = _codec_ctx->sample_rate;
        _bit_rate = _codec_ctx->bit_rate;
        _bit_per_sample = _codec_ctx->bits_per_coded_sample;
        _channel_num = ffmpeg_get_channels(_codec_ctx);
        _fmt = _codec_ctx->sample_fmt;

        _inited = true;
    } while (0);

    if (error != AE_NO) {
        al_debug("%s,error:%d", err2str(error), ret);
        cleanup();
    }

    return error;
}

int record_audio_dshow::start()
{
    if (_running == true) {
        al_warn("record audio dshow is already running");
        return AE_NO;
    }

    if (_inited == false) {
        return AE_NEED_INIT;
    }

    _running = true;
    _thread = std::thread(std::bind(&record_audio_dshow::record_loop, this));

    return AE_NO;
}

int record_audio_dshow::pause()
{
    return 0;
}

int record_audio_dshow::resume()
{
    return 0;
}

int record_audio_dshow::stop()
{
    _running = false;
    if (_thread.joinable())
        _thread.join();

    return AE_NO;
}

const AVRational record_audio_dshow::get_time_base()
{
    if (_inited && _fmt_ctx && _stream_index != -1) {
        return _fmt_ctx->streams[_stream_index]->time_base;
    } else {
        return {1, AV_TIME_BASE};
    }
}

int64_t record_audio_dshow::get_start_time()
{
    return _fmt_ctx->streams[_stream_index]->start_time;
}

int record_audio_dshow::decode(AVFrame *frame, AVPacket *packet)
{
    int ret = avcodec_send_packet(_codec_ctx, packet);
    if (ret < 0) {
        al_error("avcodec_send_packet failed:%d", ret);

        return AE_FFMPEG_DECODE_FRAME_FAILED;
    }

    while (ret >= 0) {
        ret = avcodec_receive_frame(_codec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        }

        if (ret < 0) {
            return AE_FFMPEG_READ_FRAME_FAILED;
        }

        if (ret == 0 && _on_data)
            _on_data(frame, _cb_extra_index);

        av_frame_unref(
            frame); //need to do this? avcodec_receive_frame said will call unref before receive
    }

    return AE_NO;
}

void record_audio_dshow::record_loop()
{
    int ret = 0;

    AVPacket *packet = av_packet_alloc();

    AVFrame *frame = av_frame_alloc();

    while (_running == true) {
        av_init_packet(packet);

        ret = av_read_frame(_fmt_ctx, packet);

        if (ret < 0) {
            if (_on_error)
                _on_error(AE_FFMPEG_READ_FRAME_FAILED, _cb_extra_index);
            break;
        }

        if (packet->stream_index == _stream_index) {
            ret = decode(frame, packet);
            if (ret != AE_NO) {
                if (_on_error)
                    _on_error(AE_FFMPEG_DECODE_FRAME_FAILED, _cb_extra_index);

                al_fatal("decode pcm packet failed:%d", ret);
                break;
            }
        }

        av_packet_unref(packet);
    }

    //flush packet left in decoder
    decode(frame, NULL);

    av_packet_free(&packet);
    av_frame_free(&frame);
}

void record_audio_dshow::cleanup()
{
    if (_codec_ctx)
        avcodec_close(_codec_ctx);

    if (_fmt_ctx)
        avformat_close_input(&_fmt_ctx);

    _fmt_ctx = NULL;
    _input_fmt = NULL;
    _codec_ctx = NULL;
    _codec = NULL;

    _stream_index = -1;
    _inited = false;
}
} // namespace am
