/*
 * Effect.h - base class for effects
 *
 * Copyright (c) 2006-2007 Danny McRae <khjklujn/at/users.sourceforge.net>
 * Copyright (c) 2006-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * 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 2 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 (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#ifndef LMMS_EFFECT_H
#define LMMS_EFFECT_H

#include <span>

#include "AudioEngine.h"
#include "AutomatableModel.h"
#include "Engine.h"
#include "Plugin.h"
#include "TempoSyncKnobModel.h"

namespace lmms
{

class AudioBuffer;
class EffectChain;
class EffectControls;

namespace gui
{

class EffectView;

} // namespace gui


class LMMS_EXPORT Effect : public Plugin
{
	Q_OBJECT
public:
	Effect( const Plugin::Descriptor * _desc,
			Model * _parent,
			const Descriptor::SubPluginFeatures::Key * _key );

	void saveSettings( QDomDocument & _doc, QDomElement & _parent ) override;
	void loadSettings( const QDomElement & _this ) override;

	inline QString nodeName() const override
	{
		return "effect";
	}

	//! Returns true if audio was processed and should continue being processed
	bool processAudioBuffer(AudioBuffer& inOut);

	inline bool isOkay() const
	{
		return m_okay;
	}

	inline void setOkay( bool _state )
	{
		m_okay = _state;
	}

	//! "Awake" means the effect has not been put to sleep by auto-quit
	bool isAwake() const
	{
		return m_awake;
	}

	inline bool isEnabled() const
	{
		return m_enabledModel.value();
	}

	inline f_cnt_t timeout() const
	{
		const float samples = Engine::audioEngine()->outputSampleRate() * m_autoQuitModel.value() / 1000.0f;
		return 1 + ( static_cast<int>( samples ) / Engine::audioEngine()->framesPerPeriod() );
	}

	inline float wetLevel() const
	{
		return m_wetDryModel.value();
	}

	inline float dryLevel() const
	{
		return 1.0f - m_wetDryModel.value();
	}

	inline bool dontRun() const
	{
		return m_noRun;
	}

	inline void setDontRun( bool _state )
	{
		m_noRun = _state;
	}

	bool isProcessingAudio() const
	{
		return isEnabled() && isAwake() && isOkay() && !dontRun();
	}

	inline TempoSyncKnobModel* autoQuitModel()
	{
		return &m_autoQuitModel;
	}

	bool autoQuitEnabled() const
	{
		return m_autoQuitEnabled;
	}

	EffectChain * effectChain() const
	{
		return m_parent;
	}

	virtual EffectControls * controls() = 0;

	static Effect * instantiate( const QString & _plugin_name,
				Model * _parent,
				Descriptor::SubPluginFeatures::Key * _key );


protected:
	enum class ProcessStatus
	{
		//! Unconditionally continue processing
		Continue,

		//! Calculate the RMS out sum and call `checkGate` to determine whether to stop processing
		ContinueIfNotQuiet,

		//! Do not continue processing
		Sleep
	};

	/**
	 * The main audio processing method that runs when plugin is awake and running
	 */
	virtual ProcessStatus processImpl(SampleFrame* buf, const f_cnt_t frames) = 0;

	/**
	 * Optional method that runs instead of `processImpl` when an effect
	 * is awake but not running.
	 */
	virtual void processBypassedImpl() {}


	gui::PluginView* instantiateView( QWidget * ) override;

	void goToSleep()
	{
		m_quietBufferCount = 0;
		m_awake = false;
	}

	void wakeUp()
	{
		m_quietBufferCount = 0;
		m_awake = true;
	}

	virtual void onEnabledChanged() {}

private:
	/**
	 * If auto-quit is enabled ("Keep effects running even without input" setting is disabled),
	 * after "decay" ms of the output buffer remaining below the silence threshold, the effect is
	 * turned off and won't be processed again until it receives new audio input.
	 */
	void handleAutoQuit(bool silentOutput);


	EffectChain * m_parent;

	bool m_okay;
	bool m_noRun;
	bool m_awake;

	//! The number of consecutive periods where output buffers remain below the silence threshold
	f_cnt_t m_quietBufferCount = 0;

	BoolModel m_enabledModel;
	FloatModel m_wetDryModel;
	TempoSyncKnobModel m_autoQuitModel;

	bool m_autoQuitEnabled = false;

	friend class gui::EffectView;
	friend class EffectChain;

} ;

using EffectKey = Effect::Descriptor::SubPluginFeatures::Key;
using EffectKeyList = Effect::Descriptor::SubPluginFeatures::KeyList;

} // namespace lmms

#endif // LMMS_EFFECT_H
