/*
 * SPDX-License-Identifier: GPL-3.0-only
 * MuseScore-CLA-applies
 *
 * MuseScore
 * Music Composition & Notation
 *
 * Copyright (C) 2025 MuseScore Limited and others
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * 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, see <https://www.gnu.org/licenses/>.
 */
#pragma once

#include "../iengineplayback.h"

#include "global/async/asyncable.h"

#include "global/modularity/ioc.h"
#include "../isynthresolver.h"
#include "../ifxresolver.h"
#include "../iaudioengine.h"
#include "../itracksequence.h"
#include "../iaudioengineconfiguration.h"

namespace muse::audio::soundtrack {
class SoundTrackWriter;
using SoundTrackWriterPtr = std::shared_ptr<SoundTrackWriter>;
}

namespace muse::audio::engine {
class Mixer;
class EnginePlayback : public IEnginePlayback, public Contextable, public async::Asyncable
{
    GlobalInject<IAudioEngineConfiguration> configuration;
    ContextInject<synth::ISynthResolver> synthResolver = { this };
    ContextInject<fx::IFxResolver> fxResolver = { this };
    ContextInject<IAudioEngine> audioEngine = { this };

public:
    EnginePlayback(const muse::modularity::ContextPtr& iocCtx = nullptr)
        : Contextable(iocCtx) {}

    void init();
    void deinit();

    // 1. Add Sequence
    TrackSequenceId addSequence() override;
    void removeSequence(const TrackSequenceId id) override;
    TrackSequenceIdList sequenceIdList() const override;
    ITrackSequencePtr sequence(const TrackSequenceId id) const;

    // 2. Setup tracks for Sequence
    RetVal<TrackIdList> trackIdList(const TrackSequenceId sequenceId) const override;
    RetVal<TrackName> trackName(const TrackSequenceId sequenceId, const TrackId trackId) const override;
    RetVal2<TrackId, AudioParams> addTrack(const TrackSequenceId sequenceId, const std::string& trackName, io::IODevice* playbackData,
                                           const AudioParams& params) override;
    RetVal2<TrackId, AudioParams> addTrack(const TrackSequenceId sequenceId, const std::string& trackName,
                                           const mpe::PlaybackData& playbackData, const AudioParams& params) override;
    RetVal2<TrackId, AudioOutputParams> addAuxTrack(const TrackSequenceId sequenceId, const std::string& trackName,
                                                    const AudioOutputParams& outputParams) override;

    void removeTrack(const TrackSequenceId sequenceId, const TrackId trackId) override;
    void removeAllTracks(const TrackSequenceId sequenceId) override;

    async::Channel<TrackSequenceId, TrackId> trackAdded() const override;
    async::Channel<TrackSequenceId, TrackId> trackRemoved() const override;

    AudioResourceMetaList availableInputResources() const override;
    SoundPresetList availableSoundPresets(const AudioResourceMeta& resourceMeta) const override;

    RetVal<AudioInputParams> inputParams(const TrackSequenceId sequenceId, const TrackId trackId) const override;
    void setInputParams(const TrackSequenceId sequenceId, const TrackId trackId, const AudioInputParams& params) override;
    async::Channel<TrackSequenceId, TrackId, AudioInputParams> inputParamsChanged() const override;

    void processInput(const TrackSequenceId sequenceId, const TrackId trackId) const override;
    RetVal<InputProcessingProgress> inputProcessingProgress(const TrackSequenceId sequenceId, const TrackId trackId) const override;

    void clearCache(const TrackSequenceId sequenceId, const TrackId trackId) const override;
    void clearSources() override;

    // 3. Play Sequence
    async::Promise<Ret> prepareToPlay(TrackSequenceId sequenceId) override;

    void play(TrackSequenceId sequenceId, const secs_t delay = 0.0) override;
    void seek(TrackSequenceId sequenceId, const secs_t newPosition, const bool flushSound = true) override;
    void stop(TrackSequenceId sequenceId) override;
    void pause(TrackSequenceId sequenceId) override;
    void resume(TrackSequenceId sequenceId, const secs_t delay = 0.0) override;

    void setDuration(TrackSequenceId sequenceId, const msecs_t durationMsec) override;
    Ret setLoop(TrackSequenceId sequenceId, const msecs_t fromMsec, const msecs_t toMsec) override;
    void resetLoop(TrackSequenceId sequenceId) override;

    PlaybackStatus playbackStatus(TrackSequenceId sequenceId) const override;
    async::Channel<PlaybackStatus> playbackStatusChanged(TrackSequenceId sequenceId) const override;
    secs_t playbackPosition(TrackSequenceId sequenceId) const override;
    async::Channel<secs_t> playbackPositionChanged(TrackSequenceId sequenceId) const override;

    // 4. Adjust a Sequence output
    RetVal<AudioOutputParams> outputParams(const TrackSequenceId sequenceId, const TrackId trackId) const override;
    void setOutputParams(const TrackSequenceId sequenceId, const TrackId trackId, const AudioOutputParams& params) override;
    async::Channel<TrackSequenceId, TrackId, AudioOutputParams> outputParamsChanged() const override;

    RetVal<AudioOutputParams> masterOutputParams() const override;
    void setMasterOutputParams(const AudioOutputParams& params) override;
    void clearMasterOutputParams() override;
    async::Channel<AudioOutputParams> masterOutputParamsChanged() const override;

    AudioResourceMetaList availableOutputResources() const override;

    RetVal<AudioSignalChanges> signalChanges(const TrackSequenceId sequenceId, const TrackId trackId) const override;
    RetVal<AudioSignalChanges> masterSignalChanges() const override;

    async::Promise<Ret> saveSoundTrack(const TrackSequenceId sequenceId, const io::path_t& destination,
                                       const SoundTrackFormat& format) override;
    void abortSavingAllSoundTracks() override;
    SaveSoundTrackProgress saveSoundTrackProgressChanged(const TrackSequenceId sequenceId) const override;

    void clearAllFx() override;

private:
    std::shared_ptr<Mixer> mixer() const;

    void ensureSubscriptions(const ITrackSequencePtr s);
    void ensureMixerSubscriptions();

    void listenInputProcessing(ITrackSequencePtr s, std::function<void(const Ret&)> completed);
    size_t tracksBeingProcessedCount(const ITrackSequencePtr s) const;

    Ret doSaveSoundTrack(const TrackSequenceId sequenceId, const io::path_t& destination, const SoundTrackFormat& format);

    async::Channel<TrackSequenceId, TrackId> m_trackAdded;
    async::Channel<TrackSequenceId, TrackId> m_trackRemoved;
    async::Channel<TrackSequenceId, TrackId, AudioInputParams> m_inputParamsChanged;
    async::Channel<TrackSequenceId, TrackId, AudioOutputParams> m_outputParamsChanged;
    async::Channel<AudioOutputParams> m_masterOutputParamsChanged;

    std::map<TrackSequenceId, ITrackSequencePtr> m_sequences;

    struct SaveSoundTrackProgressData {
        SaveSoundTrackProgress progress;
        async::Notification aborted;
    };

    mutable std::unordered_map<TrackSequenceId, SaveSoundTrackProgressData> m_saveSoundTracksProgressMap;
};
}
