#pragma once

#ifndef __cplusplus
#error "This is a C++ header"
#endif

extern "C" {
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavcodec/avfft.h>
#include <memory.h>
}

#include "Utils.hh"
#include <QtGlobal>
#include <QMap>
#include <QVector>
#include <QString>

namespace Vivy
{
class AudioContext;

// The memory representation of an audio file. It is created inplace and should
// not be moved around. It should be used to get the properties of an audio
// file and its streams. The user can use a stream from the file and get data
// from it.
class AudioContext final {
    VIVY_UNMOVABLE_OBJECT(AudioContext)

public:
    // Hold all the data for a stream in an audio file. Should only be owned by
    // the AudioContext class.
    class Stream final {
        VIVY_UNMOVABLE_OBJECT(Stream)

        static inline Utils::DeleterFunctionType<double> dataDeleter =
            std::bind_front(Utils::freePtrIfNotNull<double>, av_free);

        static inline Utils::DeleterFunctionType<AVCodecContext> codecContexteleter =
            std::bind_front(Utils::freePPtrIfNotNull<AVCodecContext>, avcodec_free_context);

        static inline Utils::DeleterFunctionType<AVFrame> avFrameDeleter =
            std::bind_front(Utils::freePPtrIfNotNull<AVFrame>, av_frame_free);

        static inline Utils::DeleterFunctionType<SwrContext> swrContenxtDeleter =
            std::bind_front(Utils::freePPtrIfNotNull<SwrContext>, swr_free);

        // All the used types
        using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>;
        using DataPtr           = std::shared_ptr<double[]>;
        using AVFramePtr        = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>;
        using SwrContextPtr     = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>;

    public:
        Stream(AVCodec *, AVFormatContext *, AVStream *, int index);
        ~Stream() noexcept;

        // The non-owning view of the stream's data
        using DataWeakPtr = std::weak_ptr<double[]>;

        // Decode the stream
        void decodeData();
        void cleanUpData() noexcept;
        bool isDecoded() const noexcept;
        int getDataSampleRate() const noexcept;
        size_t getDecodedDataSize() const noexcept;
        size_t getDecodedChunkSize() const noexcept;
        size_t getDecodedDecalage() const noexcept;
        double *getDecodedData() const noexcept;

        // Some getters
        int getChannels() const noexcept;
        int getSampleRate() const noexcept;
        QString getName() const noexcept;
        AVCodecID getCodecId() const noexcept;
        qint64 getBitRate() const noexcept;

        // Get the index from the audio context
        int getStreamIndex() const noexcept;

        QJsonObject getProperties() const noexcept;

    private:
        // Codec related informations
        AVCodecID codecId{ AV_CODEC_ID_NONE };
        AVCodec *codec{ nullptr };
        AVCodecParameters *codecParams{ nullptr };
        AVCodecContextPtr codecContext{ nullptr };

        // Stream is held by AudioContext
        AVStream *audioStream{ nullptr };

        // Store the index of this stream
        int streamIndexInAudioContext;

        // Resampled frame data
        AVFormatContext *dataFormat;
        SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter };
        AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter };
        double *dataPtr{ nullptr };
        size_t dataSize{ 0 };

        // Constants for the resampler
        static constexpr uint resamplerChunkSize  = 512;
        static constexpr uint resamplerOverlap    = 128;
        static constexpr uint resamplerDecalage   = resamplerChunkSize - resamplerOverlap;
        static constexpr uint resamplerSampleRate = 44100;
    };

    using StreamPtr     = std::shared_ptr<Stream>;
    using StreamWeakPtr = std::weak_ptr<Stream>;

public:
    AudioContext(const QString &path);

    StreamWeakPtr getStream(int) const noexcept;
    StreamWeakPtr getDefaultStream() const noexcept;

    QString getElementName() const noexcept;
    QJsonDocument getProperties() const noexcept;

private:
    // Regarding the format
    static inline Utils::DeleterFunctionType<AVFormatContext> avFormatContextDeleter =
        std::bind_front(Utils::freePtrIfNotNull<AVFormatContext>, avformat_free_context);

    using AVFormatContextPtr = std::unique_ptr<AVFormatContext, decltype(avFormatContextDeleter)>;
    AVFormatContextPtr format{ avformat_alloc_context(), avFormatContextDeleter };

    const QString filePath;               // Usefull information
    QMap<uint, StreamPtr> audioStreams{}; // THe audio streams of the file

    int defaultStreamIndex{ -1 };

    // Spare always null shared pointer, to be used when the audioStream[i] was
    // not found.
    StreamPtr spareNullSreamPtr{ nullptr };
};
}