#pragma once
#include <vector>

#include <common.hpp>
#include <engine/Module.hpp>
#include <engine/Cable.hpp>
#include <engine/ParamHandle.hpp>


namespace rack {
/** High-performance classes handling modules and voltage signals between them
*/
namespace engine {


/** Manages Modules and Cables and steps them in time.

Engine contains a shared mutex that locks when the Engine state is being read or written (manipulated).
Methods that share-lock (stated in their documentation) can be called simultaneously with other share-locking methods.
Methods that exclusively lock cannot be called simultaneously or recursively with another share-locking or exclusive-locking method.
*/
struct Engine {
	struct Internal;
	Internal* internal;

	PRIVATE Engine();
	PRIVATE ~Engine();

	/** Removes all modules and cables.
	Exclusively locks.
	*/
	void clear();
	PRIVATE void clear_NoLock();
	/** Advances the engine by `frames` frames.
	Only call this method from the master module.
	Share-locks. Also locks so only one stepBlock() can be called simultaneously or recursively.
	*/
	void stepBlock(int frames);
	/** Module does not need to belong to the Engine.
	However, Engine will unset the master module when it is removed from the Engine.
	NULL will unset the master module.
	Exclusively locks.
	*/
	void setMasterModule(Module* module);
	void setMasterModule_NoLock(Module* module);
	Module* getMasterModule();

	/** Returns the sample rate used by the engine for stepping each module.
	*/
	float getSampleRate();
	/** Sets the sample rate to step the modules.
	Exclusively locks.
	*/
	PRIVATE void setSampleRate(float sampleRate);
	/** Sets the sample rate if the sample rate in the settings is "Auto".
	Exclusively locks.
	*/
	void setSuggestedSampleRate(float suggestedSampleRate);
	/** Returns the inverse of the current sample rate.
	*/
	float getSampleTime();
	/** Causes worker threads to block on a mutex instead of spinlock.
	Call this in your Module::stepBlock() method to hint that the operation will take more than ~0.1 ms.
	*/
	void yieldWorkers();
	/** Returns the number of sample frames since the Engine was created.
	*/
	int64_t getFrame();
	/** Returns the number of stepBlock() calls since the Engine was created.
	*/
	int64_t getBlock();
	/** Returns the frame when stepBlock() was last called.
	*/
	int64_t getBlockFrame();
	/** Returns the time in seconds when stepBlock() was last called.
	*/
	double getBlockTime();
	/** Returns the number of frames requested by the last stepBlock() call.
	*/
	int getBlockFrames();
	/** Returns the total time that stepBlock() is advancing, in seconds.
	Calculated by `blockFrames / sampleRate`.
	*/
	double getBlockDuration();
	/** Returns the average block processing time divided by block time in the last T seconds.
	*/
	double getMeterAverage();
	double getMeterMax();

	// Modules
	size_t getNumModules();
	/** Fills `moduleIds` with up to `len` module IDs in the rack.
	Returns the number of IDs written.
	This C-like method does no allocations. The vector C++ version below does.
	Share-locks.
	*/
	size_t getModuleIds(int64_t* moduleIds, size_t len);
	/** Returns a vector of module IDs in the rack.
	Share-locks.
	*/
	std::vector<int64_t> getModuleIds();
	/** Adds a Module to the rack.
	The module ID must not be taken by another Module.
	If the module ID is -1, an ID is automatically assigned.
	Does not transfer pointer ownership.
	Exclusively locks.
	*/
	void addModule(Module* module);
	PRIVATE void addModule_NoLock(Module* module);
	/** Removes a Module from the rack.
	Exclusively locks.
	*/
	void removeModule(Module* module);
	PRIVATE void removeModule_NoLock(Module* module);
	/** Checks whether a Module is in the rack.
	Share-locks.
	*/
	bool hasModule(Module* module);
	/** Returns the Module with the given ID in the rack.
	Share-locks.
	*/
	Module* getModule(int64_t moduleId);
	Module* getModule_NoLock(int64_t moduleId);
	/** Triggers a ResetEvent for the given Module.
	Exclusively locks.
	*/
	void resetModule(Module* module);
	/** Triggers a RandomizeEvent for the given Module.
	Exclusively locks.
	*/
	void randomizeModule(Module* module);
	/** Sets the bypassed state and triggers a BypassEvent or UnBypassEvent of the given Module.
	Exclusively locks.
	*/
	void bypassModule(Module* module, bool bypassed);
	/** Serializes the given Module with locking, ensuring that Module::process() is not called simultaneously.
	Share-locks.
	*/
	json_t* moduleToJson(Module* module);
	/** Serializes the given Module with locking, ensuring that Module::process() is not called simultaneously.
	Exclusively locks.
	*/
	void moduleFromJson(Module* module, json_t* rootJ);
	/** Dispatches Save event to a module.
	Share-locks.
	*/
	void prepareSaveModule(Module* module);
	/** Dispatches Save event to all modules.
	Share-locks.
	*/
	void prepareSave();

	// Cables
	size_t getNumCables();
	/** Fills `cableIds` with up to `len` cable IDs in the rack.
	Returns the number of IDs written.
	This C-like method does no allocations. The vector C++ version below does.
	Share-locks.
	*/
	size_t getCableIds(int64_t* cableIds, size_t len);
	/** Returns a vector of cable IDs in the rack.
	Share-locks.
	*/
	std::vector<int64_t> getCableIds();
	/** Adds a Cable to the rack.
	The cable ID must not be taken by another cable.
	If the cable ID is -1, an ID is automatically assigned.
	Does not transfer pointer ownership.
	Exclusively locks.
	*/
	void addCable(Cable* cable);
	PRIVATE void addCable_NoLock(Cable* cable);
	/** Removes a Cable from the rack.
	Exclusively locks.
	*/
	void removeCable(Cable* cable);
	PRIVATE void removeCable_NoLock(Cable* cable);
	/** Checks whether a Cable is in the rack.
	Share-locks.
	*/
	bool hasCable(Cable* cable);
	/** Returns the Cable with the given ID in the rack.
	Share-locks.
	*/
	Cable* getCable(int64_t cableId);

	// Params
	void setParamValue(Module* module, int paramId, float value);
	float getParamValue(Module* module, int paramId);
	/** Requests the parameter to smoothly change toward `value`.
	*/
	void setParamSmoothValue(Module* module, int paramId, float value);
	/** Returns the target value before smoothing.
	*/
	float getParamSmoothValue(Module* module, int paramId);

	// ParamHandles
	/** Adds a ParamHandle to the rack.
	Does not automatically update the ParamHandle.
	Exclusively locks.
	*/
	void addParamHandle(ParamHandle* paramHandle);
	/**
	Exclusively locks.
	*/
	void removeParamHandle(ParamHandle* paramHandle);
	PRIVATE void removeParamHandle_NoLock(ParamHandle* paramHandle);
	/** Returns the unique ParamHandle for the given paramId
	Share-locks.
	*/
	ParamHandle* getParamHandle(int64_t moduleId, int paramId);
	ParamHandle* getParamHandle_NoLock(int64_t moduleId, int paramId);
	/** Use getParamHandle(moduleId, paramId) instead.
	Share-locks.
	*/
	DEPRECATED ParamHandle* getParamHandle(Module* module, int paramId);
	/** Sets the ParamHandle IDs and module pointer.
	If `overwrite` is true and another ParamHandle points to the same param, unsets that one and replaces it with the given handle.
	Exclusively locks.
	*/
	void updateParamHandle(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite = true);
	void updateParamHandle_NoLock(ParamHandle* paramHandle, int64_t moduleId, int paramId, bool overwrite = true);

	/** Serializes the rack.
	Share-locks.
	*/
	json_t* toJson();
	/** Deserializes the rack.
	Exclusively locks.
	*/
	void fromJson(json_t* rootJ);

	/** If no master module is set, the fallback Engine thread will step blocks, using the CPU clock for timing.
	*/
	PRIVATE void startFallbackThread();
};


} // namespace engine
} // namespace rack
