/** @file
 * @author Edouard DUPIN 
 * @copyright 2011, Edouard DUPIN, all right reserved
 * @license APACHE v2.0 (see license file)
 */

#include <audio/drain/Equalizer.hpp>
#include <audio/drain/Algo.hpp>
#include <audio/drain/debug.hpp>

// see http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt
// see http://www.earlevel.com/main/2013/10/13/biquad-calculator-v2/

audio::drain::Equalizer::Equalizer() {
	
}

void audio::drain::Equalizer::init() {
	audio::drain::Algo::init();
	audio::drain::Algo::m_type = "Equalizer";
	m_supportedFormat.push_back(audio::format_int16);
	m_supportedFormat.push_back(audio::format_int16);
	configureBiQuad();
}

ememory::SharedPtr<audio::drain::Equalizer> audio::drain::Equalizer::create() {
	ememory::SharedPtr<audio::drain::Equalizer> tmp(new audio::drain::Equalizer());
	tmp->init();
	return tmp;
}

audio::drain::Equalizer::~Equalizer() {
	
}

void audio::drain::Equalizer::configurationChange() {
	audio::drain::Algo::configurationChange();
	configureBiQuad();
}

bool audio::drain::Equalizer::process(audio::Time& _time,
                                      void* _input,
                                      size_t _inputNbChunk,
                                      void*& _output,
                                      size_t& _outputNbChunk) {
	_outputNbChunk = _inputNbChunk;
	_output = _input;
	if (_input == nullptr) {
		return false;
	}
	m_algo.process(_output, _input, _inputNbChunk);
	return true;
}

bool audio::drain::Equalizer::setParameter(const std::string& _parameter, const std::string& _value) {
	//DRAIN_WARNING("set : " << _parameter << " " << _value);
	if (_parameter == "config") {
		m_config = ejson::Object(_value);
		configureBiQuad();
	} else if (_parameter == "reset") {
		// TODO : m_algo.reset();
		return true;
	}
	return false;
}

std::string audio::drain::Equalizer::getParameter(const std::string& _parameter) const {
	return "error";
}

std::string audio::drain::Equalizer::getParameterProperty(const std::string& _parameter) const {
	return "error";
}

void audio::drain::Equalizer::addBiquad(int32_t _idBiquad, const ejson::Object& _object) {
	// get type:
	std::string typeString = _object["type"].toString().get("none");
	if (typeString == "direct-value") {
		double a0 = _object["a0"].toNumber().get(0.0);
		double a1 = _object["a1"].toNumber().get(0.0);
		double a2 = _object["a2"].toNumber().get(0.0);
		double b0 = _object["b0"].toNumber().get(0.0);
		double b1 = _object["b1"].toNumber().get(0.0);
		if (_idBiquad < 0) {
			m_algo.addBiquad(a0, a1, a2, b0, b1);
		} else {
			m_algo.addBiquad(_idBiquad, a0, a1, a2, b0, b1);
		}
	} else {
		enum audio::algo::drain::biQuadType type;
		if (etk::from_string(type, typeString) == false) {
			DRAIN_ERROR("Can not parse equalizer type:'" << typeString << "'");
		}
		double gain = _object["gain"].toNumber().get(0.0);
		double frequency = _object["cut-frequency"].toNumber().get(0.0);
		double quality = _object["quality"].toNumber().get(0.0);
		if (_idBiquad < 0) {
			m_algo.addBiquad(type, frequency, quality, gain);
		} else {
			m_algo.addBiquad(_idBiquad, type, frequency, quality, gain);
		}
	}
}

void audio::drain::Equalizer::configureBiQuad() {
	m_algo.init(getOutputFormat().getFrequency(),
	            getOutputFormat().getMap().size(),
	            getOutputFormat().getFormat());
	if (m_config.exist() == false) {
		return;
	}
	// check for a global config:
	const ejson::Array global = m_config["global"].toArray();
	if (global.exist() == true) {
		// only global configuration get all elements:
		for (size_t kkk=0; kkk<global.size(); ++kkk) {
			const ejson::Object tmpObject = global[kkk].toObject();
			if (tmpObject.exist() == false) {
				DRAIN_ERROR("Parse the configuration error : not a correct parameter:" << kkk);
				continue;
			}
			// Add biquad...
			addBiquad(-1, tmpObject);
		}
		return;
	}
	for (size_t iii=0; iii<getOutputFormat().getMap().size(); ++iii) {
		std::string channelName = etk::to_string(getOutputFormat().getMap()[iii]);
		const ejson::Array channelConfig = m_config[channelName].toArray();
		if (channelConfig.exist() == false) {
			// no config ... not a problem ...
			continue;
		}
		// only global configuration get all elements:
		for (size_t kkk=0; kkk<channelConfig.size(); ++kkk) {
			const ejson::Object tmpObject = channelConfig[kkk].toObject();
			if (tmpObject.exist() == false) {
				DRAIN_ERROR("Parse the configuration error : not a correct parameter:" << kkk);
				continue;
			}
			// add biquad:
			addBiquad(kkk, tmpObject);
		}
	}
	return;
}

std::vector<std::pair<float,float> > audio::drain::Equalizer::calculateTheory() {
	return m_algo.calculateTheory();
}