/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 5 End-User License
   Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
   27th April 2017).

   End User License Agreement: www.juce.com/juce-5-licence
   Privacy Policy: www.juce.com/juce-5-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

#include "JuceHeader.h"
#include "InternalFilters.h"
#include "FilterGraph.h"

//==============================================================================
class InternalPlugin : public AudioPluginInstance
{
protected:
	InternalPlugin(const PluginDescription& descr,
		const AudioChannelSet& channelSetToUse = AudioChannelSet::stereo())
		: AudioPluginInstance(getBusProperties(descr.numInputChannels == 0, channelSetToUse)),
		name(descr.fileOrIdentifier.upToFirstOccurrenceOf(":", false, false)),
		state(descr.fileOrIdentifier.fromFirstOccurrenceOf(":", false, false)),
		isGenerator(descr.numInputChannels == 0),
		hasMidi(descr.isInstrument),
		channelSet(channelSetToUse)
	{
		jassert(channelSetToUse.size() == descr.numOutputChannels);
	}

public:
	//==============================================================================
	const String getName() const override { return name; }
	double getTailLengthSeconds() const override { return 0.0; }
	bool acceptsMidi() const override { return hasMidi; }
	bool producesMidi() const override { return hasMidi; }
	AudioProcessorEditor* createEditor() override { return nullptr; }
	bool hasEditor() const override { return false; }
	int getNumPrograms() override { return 0; }
	int getCurrentProgram() override { return 0; }
	void setCurrentProgram(int) override {}
	const String getProgramName(int) override { return {}; }
	void changeProgramName(int, const String&) override {}
	void getStateInformation(juce::MemoryBlock&) override {}
	void setStateInformation(const void*, int) override {}

	//==============================================================================
	bool isBusesLayoutSupported(const BusesLayout& layout) const override
	{
		if (!isGenerator)
			if (layout.getMainOutputChannelSet() != channelSet)
				return false;

		if (layout.getMainInputChannelSet() != channelSet)
			return false;

		return true;
	}

	//==============================================================================
	void fillInPluginDescription(PluginDescription& description) const override
	{
		description = getPluginDescription(name + ":" + state,
			isGenerator,
			hasMidi,
			channelSet);
	}

	static PluginDescription getPluginDescription(const String& identifier,
		bool registerAsGenerator,
		bool acceptsMidi,
		const AudioChannelSet& channelSetToUse
		= AudioChannelSet::stereo())
	{
		PluginDescription descr;
		auto pluginName = identifier.upToFirstOccurrenceOf(":", false, false);
		auto pluginState = identifier.fromFirstOccurrenceOf(":", false, false);

		descr.name = pluginName;
		descr.descriptiveName = pluginName;
		descr.pluginFormatName = "Internal";
		descr.category = (registerAsGenerator ? (acceptsMidi ? "Synth" : "Generator") : "Effect");
		descr.manufacturerName = "JUCE";
		descr.version = ProjectInfo::versionString;
		descr.fileOrIdentifier = pluginName + ":" + pluginState;
		descr.uniqueId = pluginName.hashCode();
		descr.isInstrument = (acceptsMidi && registerAsGenerator);
		descr.numInputChannels = (registerAsGenerator ? 0 : channelSetToUse.size());
		descr.numOutputChannels = channelSetToUse.size();

		return descr;
	}
private:
	static BusesProperties getBusProperties(bool registerAsGenerator,
		const AudioChannelSet& channelSetToUse)
	{
		return registerAsGenerator ? BusesProperties().withOutput("Output", channelSetToUse)
			: BusesProperties().withInput("Input", channelSetToUse)
			.withOutput("Output", channelSetToUse);
	}

	//==============================================================================
	String name, state;
	bool isGenerator, hasMidi;
	AudioChannelSet channelSet;

	//==============================================================================
	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(InternalPlugin)
};

//==============================================================================
class SineWaveSynth : public InternalPlugin
{
public:
	SineWaveSynth(const PluginDescription& descr) : InternalPlugin(descr)
	{
		const int numVoices = 8;

		// Add some voices...
		for (int i = numVoices; --i >= 0;)
			synth.addVoice(new SineWaveVoice());

		// ..and give the synth a sound to play
		synth.addSound(new SineWaveSound());
	}

	static String getIdentifier()
	{
		return "Sine Wave Synth";
	}

	static PluginDescription getPluginDescription()
	{
		return InternalPlugin::getPluginDescription(getIdentifier(), true, true);
	}

	//==============================================================================
	void prepareToPlay(double newSampleRate, int) override
	{
		synth.setCurrentPlaybackSampleRate(newSampleRate);
	}

	void releaseResources() override {}

	//==============================================================================
	void processBlock(AudioBuffer<float>& buffer, MidiBuffer& midiMessages) override
	{
		const int numSamples = buffer.getNumSamples();

		buffer.clear();
		synth.renderNextBlock(buffer, midiMessages, 0, numSamples);
		buffer.applyGain(0.8f);
	}

	using InternalPlugin::processBlock;

private:
	//==============================================================================
	struct SineWaveSound : public SynthesiserSound
	{
		SineWaveSound() = default;

		bool appliesToNote(int /*midiNoteNumber*/) override { return true; }
		bool appliesToChannel(int /*midiChannel*/) override { return true; }
	};

	struct SineWaveVoice : public SynthesiserVoice
	{
		SineWaveVoice() = default;

		bool canPlaySound(SynthesiserSound* sound) override
		{
			return dynamic_cast<SineWaveSound*> (sound) != nullptr;
		}

		void startNote(int midiNoteNumber, float velocity,
			SynthesiserSound* /*sound*/,
			int /*currentPitchWheelPosition*/) override
		{
			currentAngle = 0.0;
			level = velocity * 0.15;
			tailOff = 0.0;

			double cyclesPerSecond = MidiMessage::getMidiNoteInHertz(midiNoteNumber);
			double cyclesPerSample = cyclesPerSecond / getSampleRate();

			angleDelta = cyclesPerSample * 2.0 * double_Pi;
		}

		void stopNote(float /*velocity*/, bool allowTailOff) override
		{
			if (allowTailOff)
			{
				// start a tail-off by setting this flag. The render callback will pick up on
				// this and do a fade out, calling clearCurrentNote() when it's finished.

				if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
					// stopNote method could be called more than once.
					tailOff = 1.0;
			}
			else
			{
				// we're being told to stop playing immediately, so reset everything..

				clearCurrentNote();
				angleDelta = 0.0;
			}
		}

		void pitchWheelMoved(int /*newValue*/) override
		{
			// not implemented for the purposes of this demo!
		}

		void controllerMoved(int /*controllerNumber*/, int /*newValue*/) override
		{
			// not implemented for the purposes of this demo!
		}

		void renderNextBlock(AudioBuffer<float>& outputBuffer, int startSample, int numSamples) override
		{
			if (angleDelta != 0.0)
			{
				if (tailOff > 0)
				{
					while (--numSamples >= 0)
					{
						const float currentSample = (float)(sin(currentAngle) * level * tailOff);

						for (int i = outputBuffer.getNumChannels(); --i >= 0;)
							outputBuffer.addSample(i, startSample, currentSample);

						currentAngle += angleDelta;
						++startSample;

						tailOff *= 0.99;

						if (tailOff <= 0.005)
						{
							// tells the synth that this voice has stopped
							clearCurrentNote();

							angleDelta = 0.0;
							break;
						}
					}
				}
				else
				{
					while (--numSamples >= 0)
					{
						const float currentSample = (float)(sin(currentAngle) * level);

						for (int i = outputBuffer.getNumChannels(); --i >= 0;)
							outputBuffer.addSample(i, startSample, currentSample);

						currentAngle += angleDelta;
						++startSample;
					}
				}
			}
		}

		using SynthesiserVoice::renderNextBlock;

	private:
		double currentAngle = 0, angleDelta = 0, level = 0, tailOff = 0;
	};

	//==============================================================================
	Synthesiser synth;

	//==============================================================================
	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SineWaveSynth)
};

//==============================================================================
class ReverbPlugin : public InternalPlugin
{
public:
	ReverbPlugin(const PluginDescription& descr) : InternalPlugin(descr)
	{}

	static String getIdentifier()
	{
		return "Reverb";
	}

	static PluginDescription getPluginDescription()
	{
		return InternalPlugin::getPluginDescription(getIdentifier(), false, false);
	}

	void prepareToPlay(double newSampleRate, int) override
	{
		reverb.setSampleRate(newSampleRate);
	}

	void reset() override
	{
		reverb.reset();
	}

	void releaseResources() override {}

	void processBlock(AudioBuffer<float>& buffer, MidiBuffer&) override
	{
		auto numChannels = buffer.getNumChannels();

		if (numChannels == 1)
			reverb.processMono(buffer.getWritePointer(0), buffer.getNumSamples());
		else
			reverb.processStereo(buffer.getWritePointer(0),
				buffer.getWritePointer(1),
				buffer.getNumSamples());

		for (int ch = 2; ch < numChannels; ++ch)
			buffer.clear(ch, 0, buffer.getNumSamples());
	}

	using InternalPlugin::processBlock;

private:
	Reverb reverb;
};

//==============================================================================
InternalPluginFormat::InternalPluginFormat()
{
	{
		AudioProcessorGraph::AudioGraphIOProcessor p(AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode);
		p.fillInPluginDescription(audioOutDesc);
	}

	{
		AudioProcessorGraph::AudioGraphIOProcessor p(AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode);
		p.fillInPluginDescription(audioInDesc);
	}

	{
		AudioProcessorGraph::AudioGraphIOProcessor p(AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode);
		p.fillInPluginDescription(midiInDesc);
	}
}

std::unique_ptr<AudioPluginInstance> InternalPluginFormat::createInstance(const String& name)
{
	if (name == audioOutDesc.name) return std::make_unique<AudioProcessorGraph::AudioGraphIOProcessor>(AudioProcessorGraph::AudioGraphIOProcessor::audioOutputNode);
	if (name == audioInDesc.name)  return std::make_unique<AudioProcessorGraph::AudioGraphIOProcessor>(AudioProcessorGraph::AudioGraphIOProcessor::audioInputNode);
	if (name == midiInDesc.name)   return std::make_unique<AudioProcessorGraph::AudioGraphIOProcessor>(AudioProcessorGraph::AudioGraphIOProcessor::midiInputNode);

	if (name == SineWaveSynth::getIdentifier()) return std::make_unique<SineWaveSynth>(SineWaveSynth::getPluginDescription());
	if (name == ReverbPlugin::getIdentifier())  return std::make_unique<ReverbPlugin>(ReverbPlugin::getPluginDescription());

	return {};
}

void InternalPluginFormat::createPluginInstance(const PluginDescription& desc,
	double /*initialSampleRate*/, int /*initialBufferSize*/,
	PluginCreationCallback callback)
{
	if (auto p = createInstance(desc.name))
		callback(std::move(p), {});
	else
		callback(nullptr, NEEDS_TRANS("Invalid internal plugin name"));
}

bool InternalPluginFormat::requiresUnblockedMessageThreadDuringCreation(const PluginDescription&) const
{
	return false;
}

void InternalPluginFormat::getAllTypes(Array<PluginDescription>& results)
{
	results.add(audioInDesc, audioOutDesc, midiInDesc,
		SineWaveSynth::getPluginDescription(),
		ReverbPlugin::getPluginDescription());
}

