/*
 * Copyright (C) 2016 Metrological Group B.V.
 * Copyright (C) 2016 Igalia S.L
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * aint with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#pragma once

#if ENABLE(VIDEO) && USE(GSTREAMER) && ENABLE(MEDIA_SOURCE)

#include "AbortableTaskQueue.h"
#include "GStreamerCommon.h"
#include "MediaPlayerPrivateGStreamerMSE.h"
#include "SourceBufferPrivateGStreamer.h"

#include <atomic>
#include <gst/gst.h>
#include <mutex>
#include <wtf/Condition.h>
#include <wtf/Threading.h>

namespace WebCore {

#if !LOG_DISABLED || ENABLE(ENCRYPTED_MEDIA)
struct PadProbeInformation {
    AppendPipeline* appendPipeline;
    const char* description;
    gulong probeId;
};
#endif

class AppendPipeline {
    WTF_MAKE_FAST_ALLOCATED;
public:
    AppendPipeline(SourceBufferPrivateGStreamer&, MediaPlayerPrivateGStreamerMSE&);
    virtual ~AppendPipeline();

    void pushNewBuffer(GRefPtr<GstBuffer>&&);
    void resetParserState();
    void stopParser();
    SourceBufferPrivateGStreamer& sourceBufferPrivate() { return m_sourceBufferPrivate; }
    MediaPlayerPrivateGStreamerMSE* playerPrivate() { return m_playerPrivate; }

private:
    // Similar to TrackPrivateBaseGStreamer::TrackType, but with a new value (Invalid) for when the codec is
    // not supported on this system, which should result in ParsingFailed error being thrown in SourceBuffer.
    enum StreamType { Audio, Video, Text, Unknown, Invalid };
#ifndef GST_DISABLE_GST_DEBUG
    static const char * streamTypeToString(StreamType);
#endif

    struct Track {
        // Track objects are created on pad-added for the first initialization segment, and destroyed after
        // the pipeline state has been set to GST_STATE_NULL.
        WTF_MAKE_NONCOPYABLE(Track);
        WTF_MAKE_FAST_ALLOCATED;
    public:

        Track(const AtomString& trackId, StreamType streamType, const GRefPtr<GstCaps>& caps, const FloatSize& presentationSize)
            : trackId(trackId)
            , streamType(streamType)
            , caps(caps)
            , presentationSize(presentationSize)
        { }

        AtomString trackId;
        StreamType streamType;
        GRefPtr<GstCaps> caps;
        FloatSize presentationSize;

        // Needed by some formats. To simplify the code, parser can be a GstIdentity when not needed.
        GRefPtr<GstElement> parser;
        GRefPtr<GstElement> appsink;
        GRefPtr<GstPad> entryPad; // Sink pad of the parser/GstIdentity.
        GRefPtr<GstPad> appsinkPad;

        RefPtr<WebCore::TrackPrivateBase> webKitTrack;

#if !LOG_DISABLED
        struct PadProbeInformation appsinkDataEnteringPadProbeInformation;
#endif
#if ENABLE(ENCRYPTED_MEDIA)
        struct PadProbeInformation appsinkPadEventProbeInformation;
#endif

        void emplaceOptionalParserForFormat(GstBin*, const GRefPtr<GstCaps>&);
        void initializeElements(AppendPipeline*, GstBin*);
        bool isLinked() const { return gst_pad_is_linked(entryPad.get()); }
    };

    void handleErrorSyncMessage(GstMessage*);
    void handleNeedContextSyncMessage(GstMessage*);
    // For debug purposes only:
    void handleStateChangeMessage(GstMessage*);

    void handleAppsinkNewSampleFromStreamingThread(GstElement*);
    void handleErrorCondition();
    void handleErrorConditionFromStreamingThread();

    void hookTrackEvents(Track&);
    static std::tuple<GRefPtr<GstCaps>, AppendPipeline::StreamType, FloatSize> parseDemuxerSrcPadCaps(GstCaps*);
    Ref<WebCore::TrackPrivateBase> makeWebKitTrack(int trackIndex);
    void appsinkCapsChanged(Track&);
    void appsinkNewSample(const Track&, GRefPtr<GstSample>&&);
    void handleEndOfAppend();
    void didReceiveInitializationSegment();

    GstBus* bus() { return m_bus.get(); }
    GstElement* pipeline() { return m_pipeline.get(); }
    GstElement* appsrc() { return m_appsrc.get(); }

    static AtomString generateTrackId(StreamType, int padIndex);
    enum class CreateTrackResult { TrackCreated, TrackIgnored, AppendParsingFailed };
    std::pair<CreateTrackResult, AppendPipeline::Track*> tryCreateTrackFromPad(GstPad* demuxerSrcPad, int padIndex);

    bool recycleTrackForPad(GstPad*);
    void linkPadWithTrack(GstPad* demuxerSrcPad, Track&);

    void consumeAppsinksAvailableSamples();

    GstPadProbeReturn appsrcEndOfAppendCheckerProbe(GstPadProbeInfo*);

    static void staticInitialization();

    static std::once_flag s_staticInitializationFlag;
    static GType s_endOfAppendMetaType;
    static const GstMetaInfo* s_webKitEndOfAppendMetaInfo;

    // Used only for asserting that there is only one streaming thread.
    // Only the pointers are compared.
    Thread* m_streamingThread;

    bool m_hasReceivedFirstInitializationSegment { false };
    // Used only for asserting EOS events are only caused by demuxing errors.
    bool m_errorReceived { false };

    SourceBufferPrivateGStreamer& m_sourceBufferPrivate;
    MediaPlayerPrivateGStreamerMSE* m_playerPrivate;

    MediaTime m_initialDuration;
    GRefPtr<GstElement> m_pipeline;
    GRefPtr<GstBus> m_bus;
    GRefPtr<GstElement> m_appsrc;
    // To simplify the code, mtypefind and m_demux can be a GstIdentity when not needed.
    GRefPtr<GstElement> m_typefind;
    GRefPtr<GstElement> m_demux;

    Vector<std::unique_ptr<Track>> m_tracks;

    // Used to avoid unnecessary notifications per sample.
    // It is read and written from the streaming thread and written from the main thread.
    // The main thread must set it to false before actually pulling samples.
    // This strategy ensures that at any time, there are at most two notifications in the bus
    // queue, instead of it growing unbounded.
    std::atomic_flag m_wasBusAlreadyNotifiedOfAvailableSamples;

#if !LOG_DISABLED
    struct PadProbeInformation m_demuxerDataEnteringPadProbeInformation;
#endif

    AbortableTaskQueue m_taskQueue;
};

} // namespace WebCore.

#endif // USE(GSTREAMER)
